{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "webhook-tester",
  "title": "Webhook Tester",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/webhook-tester/webhook-tester.component.ts",
      "content": "// Svix and Stripe style live webhook delivery inspector and event simulator with\n// signature generation, payload editor, and request/response inspection.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, computed, signal } from '@angular/core'\nimport {\n  Activity,\n  ArrowRight,\n  Check,\n  CheckCircle2,\n  Clock,\n  Copy,\n  FileCode,\n  Globe,\n  Key,\n  Layers,\n  LucideAngularModule,\n  RefreshCw,\n  RotateCw,\n  Send,\n  ShieldCheck,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport {\n  UiTabsComponent,\n  UiTabsContentComponent,\n  UiTabsListComponent,\n  UiTabsTriggerComponent,\n} from '@/ui/tabs/tabs.component'\nimport { UiTextareaComponent } from '@/ui/textarea/textarea.component'\n\nexport interface WebhookEventPreset {\n  id: string\n  name: string\n  category: string\n  description: string\n  defaultPayload: object\n}\n\nexport interface DeliveryAttempt {\n  id: string\n  eventId: string\n  eventType: string\n  status: number\n  statusText: string\n  timestamp: string\n  timeAgo: string\n  latencyMs: number\n  endpointUrl: string\n  signature: string\n  requestHeaders: Record<string, string>\n  requestBody: string\n  responseHeaders: Record<string, string>\n  responseBody: string\n  timings: {\n    dns: number\n    tls: number\n    ttfb: number\n    download: number\n    total: number\n  }\n}\n\nexport type WebhookSignatureFormat = 'stripe' | 'svix'\n\nconst eventPresets: WebhookEventPreset[] = [\n  {\n    id: 'customer.created',\n    name: 'customer.created',\n    category: 'Customers',\n    description: 'Dispatched whenever a new customer account or profile is created.',\n    defaultPayload: {\n      id: 'evt_1PqN8y2eZvKYlo2C79x01abc',\n      object: 'event',\n      api_version: '2026-08-01',\n      created: 1787313600,\n      type: 'customer.created',\n      data: {\n        object: {\n          id: 'cus_N0wP9XyZ12345',\n          object: 'customer',\n          name: 'Sarah Connor',\n          email: 'sarah.connor@cyberdyne.io',\n          phone: '+1 415 555 0199',\n          currency: 'usd',\n          metadata: {\n            source: 'onboarding_flow_v2',\n            tier: 'enterprise',\n          },\n        },\n      },\n    },\n  },\n  {\n    id: 'invoice.payment_succeeded',\n    name: 'invoice.payment_succeeded',\n    category: 'Billing',\n    description: 'Triggered upon successful capture and settlement of recurring invoice payment.',\n    defaultPayload: {\n      id: 'evt_2MqK7x3eZvKYlo2C88y02def',\n      object: 'event',\n      api_version: '2026-08-01',\n      created: 1787313580,\n      type: 'invoice.payment_succeeded',\n      data: {\n        object: {\n          id: 'in_1QpZ2w3eZvKYlo2C99182312',\n          object: 'invoice',\n          customer: 'cus_N0wP9XyZ12345',\n          amount_paid: 49000,\n          amount_due: 49000,\n          currency: 'usd',\n          status: 'paid',\n          paid_at: 1787313575,\n          lines: {\n            data: [\n              {\n                description: 'Pro Tier Subscription (10 Seats)',\n                amount: 49000,\n                period: {\n                  start: 1787313500,\n                  end: 1789905500,\n                },\n              },\n            ],\n          },\n        },\n      },\n    },\n  },\n  {\n    id: 'order.fulfilled',\n    name: 'order.fulfilled',\n    category: 'Fulfillment',\n    description: 'Emitted when logistics carrier records the parcel as dispatched.',\n    defaultPayload: {\n      id: 'evt_3KpJ5v4eZvKYlo2C77z03ghi',\n      object: 'event',\n      api_version: '2026-08-01',\n      created: 1787313520,\n      type: 'order.fulfilled',\n      data: {\n        object: {\n          id: 'ord_88192341',\n          object: 'order',\n          customer_id: 'cus_N0wP9XyZ12345',\n          carrier: 'FedEx Priority',\n          tracking_number: '794829103948',\n          status: 'fulfilled',\n          items_count: 3,\n          fulfillment_date: '2026-08-21T14:38:40Z',\n        },\n      },\n    },\n  },\n  {\n    id: 'subscription.updated',\n    name: 'subscription.updated',\n    category: 'Subscriptions',\n    description: 'Emitted when plan, billing frequency, or seat quantities update.',\n    defaultPayload: {\n      id: 'evt_4TqL9m1eZvKYlo2C66w04jkl',\n      object: 'event',\n      api_version: '2026-08-01',\n      created: 1787313460,\n      type: 'subscription.updated',\n      data: {\n        object: {\n          id: 'sub_1QzX89aZvKYlo2C01',\n          object: 'subscription',\n          customer: 'cus_N0wP9XyZ12345',\n          current_period_end: 1792497600,\n          plan: {\n            id: 'price_enterprise_yearly',\n            name: 'Enterprise Annual',\n            amount: 588000,\n          },\n          quantity: 25,\n          status: 'active',\n        },\n      },\n    },\n  },\n]\n\nexport function generateDeterministicSignature(secret: string, payload: string, timestamp: number): string {\n  let hash = 0\n  const str = `${timestamp}.${secret}.${payload}`\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i)\n    hash = (hash << 5) - hash + char\n    hash |= 0\n  }\n  const hex1 = Math.abs(hash).toString(16).padStart(8, '0')\n  const hex2 = Math.abs((hash * 31 + 17) | 0)\n    .toString(16)\n    .padStart(8, '0')\n  const hex3 = Math.abs((hash * 127 + 59) | 0)\n    .toString(16)\n    .padStart(8, '0')\n  const hex4 = Math.abs((hash * 8191 + 97) | 0)\n    .toString(16)\n    .padStart(8, '0')\n  return `${hex1}${hex2}${hex3}${hex4}`\n}\n\nconst initialDeliveries: DeliveryAttempt[] = [\n  {\n    id: 'del_01J6A7BC8D9EF01',\n    eventId: 'evt_1PqN8y2eZvKYlo2C79x01abc',\n    eventType: 'customer.created',\n    status: 200,\n    statusText: 'OK',\n    timestamp: '2026-08-21 14:40:12 UTC',\n    timeAgo: '12s ago',\n    latencyMs: 142,\n    endpointUrl: 'https://api.acme.dev/webhooks',\n    signature: 't=1787313612,v1=5257a869e7eceeda32ab62f1a9338f6cf60c9b07',\n    requestHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      'user-agent': 'UIPKGE-Webhooks/2.0 (webhook-tester)',\n      'stripe-signature': 't=1787313612,v1=5257a869e7eceeda32ab62f1a9338f6cf60c9b07',\n      'webhook-id': 'msg_01J6A7BC8D9EF01',\n      'webhook-timestamp': '1787313612',\n    },\n    requestBody: JSON.stringify(eventPresets[0]!.defaultPayload, null, 2),\n    responseHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      date: 'Fri, 21 Aug 2026 14:40:12 GMT',\n      server: 'cloudflare-worker',\n      'x-request-id': 'req_98b47120aef1',\n    },\n    responseBody: JSON.stringify(\n      {\n        received: true,\n        handler: 'customer_sync_v2',\n        job_id: 'job_984129',\n        processed_at: '2026-08-21T14:40:12.140Z',\n      },\n      null,\n      2,\n    ),\n    timings: {\n      dns: 14,\n      tls: 26,\n      ttfb: 88,\n      download: 14,\n      total: 142,\n    },\n  },\n  {\n    id: 'del_01J6A6ZZ7E8D9C0',\n    eventId: 'evt_2MqK7x3eZvKYlo2C88y02def',\n    eventType: 'invoice.payment_succeeded',\n    status: 200,\n    statusText: 'OK',\n    timestamp: '2026-08-21 14:38:10 UTC',\n    timeAgo: '2m ago',\n    latencyMs: 98,\n    endpointUrl: 'https://api.acme.dev/webhooks',\n    signature: 't=1787313490,v1=99fa1b2388c4710aef12d098bc762a41289fe1b0',\n    requestHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      'user-agent': 'UIPKGE-Webhooks/2.0 (webhook-tester)',\n      'stripe-signature': 't=1787313490,v1=99fa1b2388c4710aef12d098bc762a41289fe1b0',\n      'webhook-id': 'msg_01J6A6ZZ7E8D9C0',\n      'webhook-timestamp': '1787313490',\n    },\n    requestBody: JSON.stringify(eventPresets[1]!.defaultPayload, null, 2),\n    responseHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      date: 'Fri, 21 Aug 2026 14:38:10 GMT',\n      server: 'nginx/1.24.0',\n      'x-request-id': 'req_77a1902bc450',\n    },\n    responseBody: JSON.stringify(\n      {\n        status: 'ok',\n        ledger_updated: true,\n        invoice_id: 'in_1QpZ2w3eZvKYlo2C99182312',\n      },\n      null,\n      2,\n    ),\n    timings: {\n      dns: 10,\n      tls: 20,\n      ttfb: 56,\n      download: 12,\n      total: 98,\n    },\n  },\n  {\n    id: 'del_01J6A5YY4C2B1A9',\n    eventId: 'evt_3KpJ5v4eZvKYlo2C77z03ghi',\n    eventType: 'order.fulfilled',\n    status: 500,\n    statusText: 'Internal Server Error',\n    timestamp: '2026-08-21 14:34:00 UTC',\n    timeAgo: '6m ago',\n    latencyMs: 1240,\n    endpointUrl: 'https://api.acme.dev/webhooks',\n    signature: 't=1787313240,v1=12fe48a90bb76c123490fdba891230cd78129aef',\n    requestHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      'user-agent': 'UIPKGE-Webhooks/2.0 (webhook-tester)',\n      'stripe-signature': 't=1787313240,v1=12fe48a90bb76c123490fdba891230cd78129aef',\n      'webhook-id': 'msg_01J6A5YY4C2B1A9',\n      'webhook-timestamp': '1787313240',\n    },\n    requestBody: JSON.stringify(eventPresets[2]!.defaultPayload, null, 2),\n    responseHeaders: {\n      'content-type': 'application/json; charset=utf-8',\n      date: 'Fri, 21 Aug 2026 14:34:01 GMT',\n      server: 'express-gateway',\n      'x-request-id': 'req_33f8101cd991',\n    },\n    responseBody: JSON.stringify(\n      {\n        error: 'InternalServerError',\n        message: 'Failed to connect to upstream ERP database at 10.0.4.12:5432 (ETIMEDOUT)',\n        code: 'DB_CONN_TIMEOUT',\n        retryable: true,\n      },\n      null,\n      2,\n    ),\n    timings: {\n      dns: 12,\n      tls: 24,\n      ttfb: 1190,\n      download: 14,\n      total: 1240,\n    },\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-webhook-tester, [ui-webhook-tester]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiSeparatorComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n    UiTabsComponent,\n    UiTabsContentComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n    UiTextareaComponent,\n  ],\n  template: `\n    <div data-slot=\"webhook-tester\" [class]=\"rootClass\">\n      <!-- Top Header Banner & Primary Endpoint Controls -->\n      <div ui-card class=\"border-border bg-card shadow-xs\">\n        <div ui-card-header class=\"pb-4\">\n          <div class=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n            <div class=\"space-y-1\">\n              <div class=\"flex items-center gap-2\">\n                <div class=\"bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg\">\n                  <lucide-icon [img]=\"Zap\" class=\"size-4\" />\n                </div>\n                <h3 ui-card-title class=\"text-base font-semibold\">Webhook Event Simulator & Inspector</h3>\n              </div>\n              <p ui-card-description class=\"text-xs\">\n                Trigger test events and verify signature headers against your endpoint.\n              </p>\n            </div>\n\n            <div class=\"flex items-center gap-2\">\n              <span ui-badge variant=\"outline\" class=\"border-border bg-muted/50 font-mono text-xs\">\n                <span class=\"bg-success mr-1.5 inline-block size-2 animate-pulse rounded-full\"></span>\n                Ingress Ready\n              </span>\n            </div>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"pt-0\">\n          <div class=\"grid grid-cols-1 gap-3 md:grid-cols-12\">\n            <!-- Endpoint URL Input -->\n            <div class=\"md:col-span-8 lg:col-span-9\">\n              <div class=\"relative flex items-center\">\n                <span\n                  class=\"text-muted-foreground pointer-events-none absolute left-3 flex items-center gap-1.5 font-mono text-xs\"\n                >\n                  <lucide-icon [img]=\"Globe\" class=\"size-3.5\" />\n                  <span class=\"text-foreground font-semibold\">POST</span>\n                </span>\n                <ui-input\n                  [value]=\"endpointUrl()\"\n                  (valueChange)=\"endpointUrl.set($event)\"\n                  placeholder=\"https://api.acme.dev/webhooks\"\n                  class=\"pl-20 font-mono text-xs\"\n                />\n              </div>\n            </div>\n\n            <!-- Trigger Button -->\n            <div class=\"md:col-span-4 lg:col-span-3\">\n              <button\n                ui-button\n                size=\"default\"\n                class=\"w-full gap-2 text-xs font-medium shadow-xs\"\n                [disabled]=\"isSending() || !endpointUrl()\"\n                (click)=\"sendWebhook()\"\n              >\n                @if (isSending()) {\n                  <span\n                    class=\"border-primary-foreground size-3.5 animate-spin rounded-full border-2 border-t-transparent\"\n                  ></span>\n                } @else {\n                  <lucide-icon [img]=\"Send\" class=\"size-3.5\" />\n                }\n                <span>{{ isSending() ? 'Delivering...' : 'Send Test Webhook' }}</span>\n                <kbd class=\"bg-primary-foreground/20 hidden rounded px-1 py-0.5 font-mono text-xs sm:inline-flex\"\n                  >⌘↵</kbd\n                >\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- 2-Column Inspector Layout -->\n      <div class=\"grid grid-cols-1 gap-6 lg:grid-cols-12\">\n        <!-- Left Panel: Event Payload Selector, JSON Editor & Signing Secret -->\n        <div class=\"space-y-6 lg:col-span-6\">\n          <!-- Event Selector & Payload Schema Card -->\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"Layers\" class=\"size-4\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Event Configuration</h3>\n                    <p ui-card-description class=\"text-xs\">Select event payload and adjust test parameters</p>\n                  </div>\n                </div>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">{{ activePreset().category }}</span>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-0\">\n              <!-- Event Dropdown -->\n              <div class=\"space-y-1.5\">\n                <label class=\"text-foreground text-xs font-medium\">Event Type</label>\n                <ui-select [value]=\"selectedEvent()\" (valueChange)=\"handleEventChange($event)\">\n                  <button ui-select-trigger class=\"font-mono text-xs\">\n                    <ui-select-value placeholder=\"Select event preset\" />\n                  </button>\n                  <ui-select-content>\n                    @for (preset of presets; track preset.id) {\n                      <ui-select-item [value]=\"preset.id\" class=\"font-mono text-xs\">\n                        {{ preset.name }}\n                      </ui-select-item>\n                    }\n                  </ui-select-content>\n                </ui-select>\n                <p class=\"text-muted-foreground text-xs\">{{ activePreset().description }}</p>\n              </div>\n\n              <!-- Quick Selection Badges -->\n              <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                @for (preset of presets; track preset.id) {\n                  <button type=\"button\" [class]=\"presetChipClass(preset.id)\" (click)=\"handleEventChange(preset.id)\">\n                    {{ preset.id }}\n                  </button>\n                }\n              </div>\n\n              <ui-separator />\n\n              <!-- JSON Payload Editor -->\n              <div class=\"space-y-2\">\n                <div class=\"flex items-center justify-between\">\n                  <label class=\"text-foreground flex items-center gap-1.5 text-xs font-medium\">\n                    <lucide-icon [img]=\"FileCode\" class=\"text-primary size-3.5\" />\n                    <span>Payload Body (JSON)</span>\n                  </label>\n\n                  <div class=\"flex items-center gap-1.5\">\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                      (click)=\"handleFormatJson()\"\n                    >\n                      Format\n                    </button>\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                      (click)=\"handleResetPayload()\"\n                    >\n                      <lucide-icon [img]=\"RefreshCw\" class=\"mr-1 size-3\" />\n                      Reset\n                    </button>\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                      (click)=\"handleCopy(currentPayloadStr(), 'payload')\"\n                    >\n                      @if (copiedKey() === 'payload') {\n                        <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                      } @else {\n                        <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                      }\n                      <span>{{ copiedKey() === 'payload' ? 'Copied' : 'Copy' }}</span>\n                    </button>\n                  </div>\n                </div>\n\n                <ui-textarea\n                  [value]=\"currentPayloadStr()\"\n                  (valueChange)=\"currentPayloadStr.set($event)\"\n                  [rows]=\"11\"\n                  [noResize]=\"true\"\n                  class=\"border-border/70 focus:border-primary font-mono text-xs leading-relaxed\"\n                  placeholder='{\n  \"type\": \"event.name\"\n}'\n                />\n              </div>\n            </div>\n\n            <div\n              ui-card-footer\n              class=\"border-border text-muted-foreground flex items-center justify-between border-t pt-3 text-xs\"\n            >\n              <span class=\"font-mono\">{{ currentPayloadStr().length }} bytes</span>\n              <span class=\"font-mono\">Content-Type: application/json</span>\n            </div>\n          </div>\n\n          <!-- Signing Secret & Signature Verification Preview Card -->\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"ShieldCheck\" class=\"size-4\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Signature & Security Headers</h3>\n                    <p ui-card-description class=\"text-xs\">HMAC-SHA256 signature calculated from secret & timestamp</p>\n                  </div>\n                </div>\n                <div class=\"flex items-center gap-1\">\n                  <button type=\"button\" [class]=\"formatChipClass('stripe')\" (click)=\"signatureFormat.set('stripe')\">\n                    Stripe\n                  </button>\n                  <button type=\"button\" [class]=\"formatChipClass('svix')\" (click)=\"signatureFormat.set('svix')\">\n                    Svix Standard\n                  </button>\n                </div>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-4 pt-0\">\n              <!-- Signing Secret Input -->\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label class=\"text-foreground text-xs font-medium\">Endpoint Signing Secret</label>\n                  <button\n                    type=\"button\"\n                    class=\"text-primary flex min-h-6 cursor-pointer items-center gap-1 text-xs hover:underline\"\n                    (click)=\"handleRegenerateSecret()\"\n                  >\n                    <lucide-icon [img]=\"RotateCw\" class=\"size-3\" />\n                    <span>Regenerate</span>\n                  </button>\n                </div>\n                <div class=\"relative flex items-center\">\n                  <lucide-icon [img]=\"Key\" class=\"text-muted-foreground pointer-events-none absolute left-3 size-3.5\" />\n                  <ui-input\n                    [value]=\"signingSecret()\"\n                    (valueChange)=\"signingSecret.set($event)\"\n                    class=\"pr-16 pl-8 font-mono text-xs\"\n                    placeholder=\"whsec_...\"\n                  />\n                  <button\n                    ui-button\n                    aria-label=\"Copy signing secret\"\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground absolute right-1 h-7 px-2 text-xs\"\n                    (click)=\"handleCopy(signingSecret(), 'secret')\"\n                  >\n                    @if (copiedKey() === 'secret') {\n                      <lucide-icon [img]=\"Check\" class=\"text-success size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                    }\n                  </button>\n                </div>\n              </div>\n\n              <!-- Signature Header Box -->\n              <div class=\"space-y-1.5\">\n                <div class=\"flex items-center justify-between\">\n                  <label class=\"text-foreground text-xs font-medium\">Generated Request Headers</label>\n                  <button\n                    ui-button\n                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                    (click)=\"handleCopy(computedAllHeaders(), 'headers')\"\n                  >\n                    @if (copiedKey() === 'headers') {\n                      <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                    } @else {\n                      <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                    }\n                    <span>{{ copiedKey() === 'headers' ? 'Copied' : 'Copy Headers' }}</span>\n                  </button>\n                </div>\n                <pre\n                  class=\"border-border bg-muted/40 text-foreground overflow-x-auto rounded-md border p-3 font-mono text-xs leading-relaxed\"\n                ><code>{{ computedAllHeaders() }}</code></pre>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- Right Panel: Delivery History & Active Delivery Inspector -->\n        <div class=\"space-y-6 lg:col-span-6\">\n          <!-- Recent Delivery Attempts List -->\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex items-center justify-between\">\n                <div class=\"flex items-center gap-2\">\n                  <div class=\"bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md\">\n                    <lucide-icon [img]=\"Activity\" class=\"size-4\" />\n                  </div>\n                  <div>\n                    <h3 ui-card-title class=\"text-sm font-semibold\">Delivery History</h3>\n                    <p ui-card-description class=\"text-xs\">Recent dispatch logs and response telemetry</p>\n                  </div>\n                </div>\n                <span class=\"text-muted-foreground font-mono text-xs\"\n                  >{{ deliveryHistory().length }} events logged</span\n                >\n              </div>\n            </div>\n\n            <div ui-card-content class=\"p-0\">\n              <div class=\"divide-border max-h-[250px] divide-y overflow-y-auto\">\n                @for (attempt of deliveryHistory(); track attempt.id) {\n                  <div [class]=\"attemptRowClass(attempt.id)\" (click)=\"selectedDeliveryId.set(attempt.id)\">\n                    <div class=\"flex min-w-0 items-center gap-2.5\">\n                      <span\n                        ui-badge\n                        [variant]=\"attempt.status === 200 ? 'success' : 'destructive'\"\n                        class=\"shrink-0 px-1.5 py-0.5 font-mono text-xs\"\n                      >\n                        {{ attempt.status }}\n                      </span>\n                      <div class=\"min-w-0 truncate\">\n                        <p class=\"text-foreground truncate font-mono\">{{ attempt.eventType }}</p>\n                        <p class=\"text-muted-foreground truncate font-mono text-xs\">{{ attempt.id }}</p>\n                      </div>\n                    </div>\n\n                    <div class=\"flex shrink-0 items-center gap-3 text-right\">\n                      <span class=\"text-muted-foreground font-mono\">{{ attempt.latencyMs }}ms</span>\n                      <span class=\"text-muted-foreground text-xs\">{{ attempt.timeAgo }}</span>\n                      <lucide-icon [img]=\"ArrowRight\" class=\"text-muted-foreground size-3.5\" />\n                    </div>\n                  </div>\n                }\n              </div>\n            </div>\n          </div>\n\n          <!-- Active Delivery Detail Inspector -->\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-3\">\n              <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n                <div class=\"space-y-1\">\n                  <div class=\"flex items-center gap-2\">\n                    <span\n                      ui-badge\n                      [variant]=\"activeDelivery().status === 200 ? 'success' : 'destructive'\"\n                      class=\"font-mono text-xs\"\n                    >\n                      {{ activeDelivery().status }} {{ activeDelivery().statusText }}\n                    </span>\n                    <span class=\"text-foreground font-mono text-xs font-semibold\">{{ activeDelivery().id }}</span>\n                  </div>\n                  <p class=\"text-muted-foreground truncate font-mono text-xs\">{{ activeDelivery().endpointUrl }}</p>\n                </div>\n\n                <div class=\"flex items-center gap-2\">\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    class=\"h-8 gap-1.5 text-xs font-medium\"\n                    (click)=\"retryDelivery(activeDelivery())\"\n                  >\n                    <lucide-icon [img]=\"RotateCw\" class=\"size-3.5\" />\n                    <span>Retry</span>\n                  </button>\n                </div>\n              </div>\n            </div>\n\n            <div ui-card-content class=\"pt-0\">\n              <ui-tabs [value]=\"activeInspectorTab()\" (valueChange)=\"activeInspectorTab.set($event)\" class=\"w-full\">\n                <ui-tabs-list class=\"grid w-full grid-cols-3\">\n                  <button ui-tabs-trigger value=\"request\" class=\"text-xs\">Request</button>\n                  <button ui-tabs-trigger value=\"response\" class=\"text-xs\">Response</button>\n                  <button ui-tabs-trigger value=\"timings\" class=\"text-xs\">Diagnostics</button>\n                </ui-tabs-list>\n\n                <!-- Request Tab -->\n                <ui-tabs-content value=\"request\" class=\"space-y-4 pt-3\">\n                  <!-- Request Headers Table -->\n                  <div class=\"space-y-1.5\">\n                    <div class=\"flex items-center justify-between\">\n                      <label class=\"text-foreground text-xs font-medium\">Request Headers</label>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                        (click)=\"handleCopy(stringify(activeDelivery().requestHeaders), 'req-headers')\"\n                      >\n                        @if (copiedKey() === 'req-headers') {\n                          <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                        }\n                        <span>{{ copiedKey() === 'req-headers' ? 'Copied' : 'Copy' }}</span>\n                      </button>\n                    </div>\n                    <div class=\"border-border bg-muted/20 overflow-hidden rounded-md border\">\n                      <table ui-table>\n                        <thead ui-table-header>\n                          <tr ui-table-row>\n                            <th ui-table-head class=\"h-8 text-xs font-medium\">Header</th>\n                            <th ui-table-head class=\"h-8 text-xs font-medium\">Value</th>\n                          </tr>\n                        </thead>\n                        <tbody ui-table-body>\n                          @for (row of headerEntries(activeDelivery().requestHeaders); track row[0]) {\n                            <tr ui-table-row>\n                              <td ui-table-cell class=\"text-muted-foreground py-1.5 font-mono text-xs\">{{ row[0] }}</td>\n                              <td ui-table-cell class=\"text-foreground max-w-[200px] truncate py-1.5 font-mono text-xs\">\n                                {{ row[1] }}\n                              </td>\n                            </tr>\n                          }\n                        </tbody>\n                      </table>\n                    </div>\n                  </div>\n\n                  <!-- Request Body -->\n                  <div class=\"space-y-1.5\">\n                    <div class=\"flex items-center justify-between\">\n                      <label class=\"text-foreground text-xs font-medium\">Request Body Payload</label>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                        (click)=\"handleCopy(activeDelivery().requestBody, 'req-body')\"\n                      >\n                        @if (copiedKey() === 'req-body') {\n                          <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                        }\n                        <span>{{ copiedKey() === 'req-body' ? 'Copied' : 'Copy' }}</span>\n                      </button>\n                    </div>\n                    <pre\n                      class=\"border-border bg-muted/40 text-foreground max-h-[220px] overflow-auto rounded-md border p-3 font-mono text-xs leading-relaxed\"\n                    ><code>{{ activeDelivery().requestBody }}</code></pre>\n                  </div>\n                </ui-tabs-content>\n\n                <!-- Response Tab -->\n                <ui-tabs-content value=\"response\" class=\"space-y-4 pt-3\">\n                  <!-- Response Headers Table -->\n                  <div class=\"space-y-1.5\">\n                    <div class=\"flex items-center justify-between\">\n                      <label class=\"text-foreground text-xs font-medium\">Response Headers</label>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                        (click)=\"handleCopy(stringify(activeDelivery().responseHeaders), 'res-headers')\"\n                      >\n                        @if (copiedKey() === 'res-headers') {\n                          <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                        }\n                        <span>{{ copiedKey() === 'res-headers' ? 'Copied' : 'Copy' }}</span>\n                      </button>\n                    </div>\n                    <div class=\"border-border bg-muted/20 overflow-hidden rounded-md border\">\n                      <table ui-table>\n                        <thead ui-table-header>\n                          <tr ui-table-row>\n                            <th ui-table-head class=\"h-8 text-xs font-medium\">Header</th>\n                            <th ui-table-head class=\"h-8 text-xs font-medium\">Value</th>\n                          </tr>\n                        </thead>\n                        <tbody ui-table-body>\n                          @for (row of headerEntries(activeDelivery().responseHeaders); track row[0]) {\n                            <tr ui-table-row>\n                              <td ui-table-cell class=\"text-muted-foreground py-1.5 font-mono text-xs\">{{ row[0] }}</td>\n                              <td ui-table-cell class=\"text-foreground max-w-[200px] truncate py-1.5 font-mono text-xs\">\n                                {{ row[1] }}\n                              </td>\n                            </tr>\n                          }\n                        </tbody>\n                      </table>\n                    </div>\n                  </div>\n\n                  <!-- Response Body Payload -->\n                  <div class=\"space-y-1.5\">\n                    <div class=\"flex items-center justify-between\">\n                      <label class=\"text-foreground text-xs font-medium\">Response Body</label>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                        (click)=\"handleCopy(activeDelivery().responseBody, 'res-body')\"\n                      >\n                        @if (copiedKey() === 'res-body') {\n                          <lucide-icon [img]=\"Check\" class=\"text-success mr-1 size-3\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3\" />\n                        }\n                        <span>{{ copiedKey() === 'res-body' ? 'Copied' : 'Copy' }}</span>\n                      </button>\n                    </div>\n                    <pre [class]=\"responseBodyClass\"><code>{{ activeDelivery().responseBody }}</code></pre>\n                  </div>\n                </ui-tabs-content>\n\n                <!-- Diagnostics & Timeline Tab -->\n                <ui-tabs-content value=\"timings\" class=\"space-y-4 pt-3\">\n                  <div class=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n                    <div class=\"border-border bg-muted/30 rounded-lg border p-2.5 text-center\">\n                      <p class=\"text-muted-foreground text-xs\">DNS Lookup</p>\n                      <p class=\"text-foreground font-mono text-sm font-semibold\">\n                        {{ activeDelivery().timings.dns }}ms\n                      </p>\n                    </div>\n                    <div class=\"border-border bg-muted/30 rounded-lg border p-2.5 text-center\">\n                      <p class=\"text-muted-foreground text-xs\">TLS Handshake</p>\n                      <p class=\"text-foreground font-mono text-sm font-semibold\">\n                        {{ activeDelivery().timings.tls }}ms\n                      </p>\n                    </div>\n                    <div class=\"border-border bg-muted/30 rounded-lg border p-2.5 text-center\">\n                      <p class=\"text-muted-foreground text-xs\">Server TTFB</p>\n                      <p class=\"text-foreground font-mono text-sm font-semibold\">\n                        {{ activeDelivery().timings.ttfb }}ms\n                      </p>\n                    </div>\n                    <div class=\"border-border bg-muted/30 rounded-lg border p-2.5 text-center\">\n                      <p class=\"text-muted-foreground text-xs\">Total Roundtrip</p>\n                      <p class=\"text-primary font-mono text-sm font-semibold\">{{ activeDelivery().timings.total }}ms</p>\n                    </div>\n                  </div>\n\n                  <div class=\"border-border bg-muted/20 space-y-2 rounded-lg border p-3\">\n                    <h4 class=\"text-foreground text-xs font-semibold\">Security & Delivery Verification</h4>\n                    <ul class=\"space-y-1.5 text-xs\">\n                      <li class=\"text-success flex items-center gap-2\">\n                        <lucide-icon [img]=\"CheckCircle2\" class=\"size-3.5 shrink-0\" />\n                        <span>HMAC signature matched endpoint verification secret</span>\n                      </li>\n                      <li class=\"text-success flex items-center gap-2\">\n                        <lucide-icon [img]=\"CheckCircle2\" class=\"size-3.5 shrink-0\" />\n                        <span>TLS 1.3 certificate chain verified (Cloudflare Inc)</span>\n                      </li>\n                      <li class=\"text-success flex items-center gap-2\">\n                        <lucide-icon [img]=\"CheckCircle2\" class=\"size-3.5 shrink-0\" />\n                        <span>Timestamp within tolerance envelope (300s window)</span>\n                      </li>\n                    </ul>\n                  </div>\n                </ui-tabs-content>\n              </ui-tabs>\n            </div>\n\n            <div\n              ui-card-footer\n              class=\"border-border text-muted-foreground flex items-center justify-between border-t pt-3 text-xs\"\n            >\n              <span class=\"flex items-center gap-1.5\">\n                <lucide-icon [img]=\"Clock\" class=\"size-3.5\" />\n                <span>Dispatched at: {{ activeDelivery().timestamp }}</span>\n              </span>\n              <span class=\"font-mono\">Latency: {{ activeDelivery().latencyMs }}ms</span>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiWebhookTesterComponent implements OnInit, OnDestroy {\n  protected readonly Activity = Activity\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Clock = Clock\n  protected readonly Copy = Copy\n  protected readonly FileCode = FileCode\n  protected readonly Globe = Globe\n  protected readonly Key = Key\n  protected readonly Layers = Layers\n  protected readonly RefreshCw = RefreshCw\n  protected readonly RotateCw = RotateCw\n  protected readonly Send = Send\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Zap = Zap\n\n  protected readonly presets = eventPresets\n\n  @Input('class') className?: string\n\n  readonly endpointUrl = signal('https://api.acme.dev/webhooks')\n  readonly signingSecret = signal('whsec_9f83a8b271d4e680c102a9b6c7a3efd8')\n  readonly selectedEvent = signal('customer.created')\n  readonly currentPayloadStr = signal(JSON.stringify(eventPresets[0]!.defaultPayload, null, 2))\n  readonly signatureFormat = signal<WebhookSignatureFormat>('stripe')\n  readonly activeInspectorTab = signal('request')\n\n  readonly isSending = signal(false)\n  readonly copiedKey = signal<string | null>(null)\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n\n  readonly deliveryHistory = signal<DeliveryAttempt[]>([...initialDeliveries])\n  readonly selectedDeliveryId = signal<string>(initialDeliveries[0]!.id)\n  readonly currentTimestamp = signal(1787313600)\n\n  private readonly keyHandler = (e: KeyboardEvent) => {\n    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {\n      e.preventDefault()\n      this.sendWebhook()\n    }\n  }\n\n  ngOnInit(): void {\n    window.addEventListener('keydown', this.keyHandler)\n  }\n\n  ngOnDestroy(): void {\n    window.removeEventListener('keydown', this.keyHandler)\n    clearTimeout(this.copyTimer)\n  }\n\n  readonly activeDelivery = computed(() => {\n    const history = this.deliveryHistory()\n    return history.find((d) => d.id === this.selectedDeliveryId()) ?? history[0]!\n  })\n\n  readonly activePreset = computed(() => {\n    return eventPresets.find((p) => p.id === this.selectedEvent()) ?? eventPresets[0]!\n  })\n\n  readonly computedSignatureHex = computed(() => {\n    return generateDeterministicSignature(this.signingSecret(), this.currentPayloadStr(), this.currentTimestamp())\n  })\n\n  readonly computedSignatureHeader = computed(() => {\n    if (this.signatureFormat() === 'stripe') {\n      return `Stripe-Signature: t=${this.currentTimestamp()},v1=${this.computedSignatureHex()}`\n    }\n    const b64 = typeof window !== 'undefined' ? btoa(this.computedSignatureHex().slice(0, 32)) : ''\n    return `webhook-signature: v1,${b64}`\n  })\n\n  readonly computedAllHeaders = computed(() => {\n    const t = this.currentTimestamp()\n    const sigHex = this.computedSignatureHex()\n    if (this.signatureFormat() === 'stripe') {\n      return `POST ${this.endpointUrl()}\\nHost: api.acme.dev\\nContent-Type: application/json\\nUser-Agent: UIPKGE-Webhooks/2.0\\nStripe-Signature: t=${t},v1=${sigHex}`\n    }\n    const b64 = typeof window !== 'undefined' ? btoa(sigHex.slice(0, 32)) : sigHex.slice(0, 32)\n    return `POST ${this.endpointUrl()}\\nHost: api.acme.dev\\nContent-Type: application/json\\nUser-Agent: UIPKGE-Webhooks/2.0\\nwebhook-id: msg_${sigHex.slice(0, 14)}\\nwebhook-timestamp: ${t}\\nwebhook-signature: v1,${b64}`\n  })\n\n  get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  get responseBodyClass(): string {\n    return cn(\n      'max-h-[220px] overflow-auto rounded-md border p-3 font-mono text-xs leading-relaxed',\n      this.activeDelivery().status === 200\n        ? 'border-border bg-muted/40 text-foreground'\n        : 'border-destructive/30 bg-destructive/10 text-destructive',\n    )\n  }\n\n  presetChipClass(id: string): string {\n    return cn(\n      'min-h-6 cursor-pointer rounded border px-2 py-0.5 font-mono text-xs transition-colors',\n      this.selectedEvent() === id\n        ? 'border-primary bg-primary/10 text-primary font-medium'\n        : 'border-border bg-muted/30 text-muted-foreground hover:bg-muted hover:text-foreground',\n    )\n  }\n\n  formatChipClass(format: WebhookSignatureFormat): string {\n    return cn(\n      'min-h-6 cursor-pointer rounded px-2 py-0.5 font-mono text-xs transition-colors',\n      this.signatureFormat() === format\n        ? 'bg-primary text-primary-foreground font-medium'\n        : 'bg-muted text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  attemptRowClass(id: string): string {\n    return cn(\n      'flex cursor-pointer items-center justify-between gap-3 p-3.5 text-xs transition-colors',\n      this.selectedDeliveryId() === id ? 'bg-muted/80 font-medium' : 'hover:bg-muted/40',\n    )\n  }\n\n  headerEntries(headers: Record<string, string>): [string, string][] {\n    return Object.entries(headers)\n  }\n\n  stringify(value: object): string {\n    return JSON.stringify(value, null, 2)\n  }\n\n  handleEventChange(eventId: string): void {\n    this.selectedEvent.set(eventId)\n    const preset = eventPresets.find((p) => p.id === eventId)\n    if (preset) {\n      this.currentPayloadStr.set(JSON.stringify(preset.defaultPayload, null, 2))\n    }\n  }\n\n  handleFormatJson(): void {\n    try {\n      const parsed = JSON.parse(this.currentPayloadStr())\n      this.currentPayloadStr.set(JSON.stringify(parsed, null, 2))\n    } catch {\n      // Keep as is if invalid\n    }\n  }\n\n  handleResetPayload(): void {\n    const preset = eventPresets.find((p) => p.id === this.selectedEvent())\n    if (preset) {\n      this.currentPayloadStr.set(JSON.stringify(preset.defaultPayload, null, 2))\n    }\n  }\n\n  handleRegenerateSecret(): void {\n    const chars = 'abcdef0123456789'\n    let result = 'whsec_'\n    for (let i = 0; i < 32; i++) {\n      result += chars.charAt(Math.floor(Math.random() * chars.length))\n    }\n    this.signingSecret.set(result)\n  }\n\n  handleCopy(text: string, key: string): void {\n    try {\n      navigator.clipboard?.writeText(text)\n    } catch {\n      // Clipboard blocked: the payload stays visible and selectable.\n    }\n    this.copiedKey.set(key)\n    clearTimeout(this.copyTimer)\n    const current = key\n    this.copyTimer = setTimeout(() => {\n      if (this.copiedKey() === current) this.copiedKey.set(null)\n    }, 2000)\n  }\n\n  sendWebhook(): void {\n    if (this.isSending()) return\n    this.isSending.set(true)\n\n    const nowSec = Math.floor(Date.now() / 1000)\n    this.currentTimestamp.set(nowSec)\n\n    setTimeout(() => {\n      let parsedBody: Record<string, string> = {}\n      let isInvalidJson = false\n      try {\n        parsedBody = JSON.parse(this.currentPayloadStr()) as Record<string, string>\n      } catch {\n        isInvalidJson = true\n      }\n\n      const url = this.endpointUrl()\n      const isFailureUrl = url.includes('fail') || url.includes('error')\n      const status = isInvalidJson ? 400 : isFailureUrl ? 500 : 200\n      const statusText = isInvalidJson ? 'Bad Request' : isFailureUrl ? 'Internal Server Error' : 'OK'\n      const latency = isInvalidJson ? 32 : isFailureUrl ? 850 : Math.floor(Math.random() * 90) + 65\n\n      const deliveryId = `del_${Math.random().toString(36).substring(2, 11).toUpperCase()}`\n      const eventId = parsedBody['id'] ?? `evt_${Math.random().toString(36).substring(2, 10)}`\n      const eventType = parsedBody['type'] ?? this.selectedEvent()\n      const sig = `t=${nowSec},v1=${this.computedSignatureHex()}`\n\n      const newDelivery: DeliveryAttempt = {\n        id: deliveryId,\n        eventId,\n        eventType,\n        status,\n        statusText,\n        timestamp: 'Just now',\n        timeAgo: 'Just now',\n        latencyMs: latency,\n        endpointUrl: url,\n        signature: sig,\n        requestHeaders: {\n          'content-type': 'application/json; charset=utf-8',\n          'user-agent': 'UIPKGE-Webhooks/2.0 (webhook-tester)',\n          'stripe-signature': sig,\n          'webhook-id': `msg_${deliveryId}`,\n          'webhook-timestamp': String(nowSec),\n        },\n        requestBody: this.currentPayloadStr(),\n        responseHeaders: {\n          'content-type': 'application/json; charset=utf-8',\n          date: new Date().toUTCString(),\n          server: 'acme-edge-router/1.8',\n          'x-request-id': `req_${Math.random().toString(36).substring(2, 12)}`,\n        },\n        responseBody: isInvalidJson\n          ? JSON.stringify({ error: 'BadRequest', message: 'Payload is not valid RFC 8259 JSON' }, null, 2)\n          : isFailureUrl\n            ? JSON.stringify(\n                { error: 'InternalServerError', message: 'Webhook handler threw uncaught exception' },\n                null,\n                2,\n              )\n            : JSON.stringify(\n                {\n                  received: true,\n                  event_type: eventType,\n                  delivery_id: deliveryId,\n                  status: 'acknowledged',\n                  timestamp: new Date().toISOString(),\n                },\n                null,\n                2,\n              ),\n        timings: {\n          dns: Math.floor(latency * 0.1),\n          tls: Math.floor(latency * 0.2),\n          ttfb: Math.floor(latency * 0.6),\n          download: Math.floor(latency * 0.1),\n          total: latency,\n        },\n      }\n\n      this.deliveryHistory.update((prev) => [newDelivery, ...prev])\n      this.selectedDeliveryId.set(newDelivery.id)\n      this.isSending.set(false)\n    }, 450)\n  }\n\n  retryDelivery(attempt: DeliveryAttempt): void {\n    this.currentPayloadStr.set(attempt.requestBody)\n    this.endpointUrl.set(attempt.endpointUrl)\n    this.selectedEvent.set(attempt.eventType)\n    this.sendWebhook()\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/webhook-tester/webhook-tester.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/webhook-tester/index.ts",
      "content": "export {\n  UiWebhookTesterComponent,\n  generateDeterministicSignature,\n  type WebhookEventPreset,\n  type DeliveryAttempt,\n  type WebhookSignatureFormat,\n} from './webhook-tester.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/webhook-tester/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/select.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json",
    "https://uipkge.dev/r/angular/textarea.json"
  ],
  "description": "Svix and Stripe style live webhook delivery inspector and event simulator with signature generation, payload editor, and request/response inspection.",
  "categories": [
    "devops",
    "api",
    "app",
    "developer"
  ]
}