{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "webhook-tester",
  "title": "Webhook Tester",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/webhook-tester/WebhookTester.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, ref, watch } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport {\n  Activity,\n  AlertCircle,\n  ArrowRight,\n  Check,\n  CheckCircle2,\n  Clock,\n  Copy,\n  FileCode,\n  Globe,\n  Key,\n  Layers,\n  Play,\n  RefreshCw,\n  RotateCw,\n  Send,\n  Server,\n  ShieldCheck,\n  Terminal,\n  Zap,\n} from 'lucide-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'\nimport { Textarea } from '@/components/ui/textarea'\nimport { cn } from '@/lib/utils'\n\ninterface EventPreset {\n  id: string\n  name: string\n  category: string\n  description: string\n  defaultPayload: object\n}\n\ninterface 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\nconst props = defineProps<{\n  class?: HTMLAttributes['class']\n}>()\n\nconst eventPresets: EventPreset[] = [\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\nfunction 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\n// Initial state\nconst endpointUrl = ref('https://api.acme.dev/webhooks')\nconst signingSecret = ref('whsec_9f83a8b271d4e680c102a9b6c7a3efd8')\nconst selectedEvent = ref('customer.created')\nconst currentPayloadStr = ref(JSON.stringify(eventPresets[0].defaultPayload, null, 2))\nconst signatureFormat = ref<'stripe' | 'svix'>('stripe')\nconst activeInspectorTab = ref('request')\n\nconst isSending = ref(false)\nconst copiedKey = ref<string | null>(null)\n\n// Initial mock delivery history\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\nconst deliveryHistory = ref<DeliveryAttempt[]>([...initialDeliveries])\nconst selectedDeliveryId = ref<string>(initialDeliveries[0].id)\n\nconst currentTimestamp = ref(1787313600)\n\nconst activeDelivery = computed(() => {\n  return deliveryHistory.value.find((d) => d.id === selectedDeliveryId.value) ?? deliveryHistory.value[0]\n})\n\nconst activePreset = computed(() => {\n  return eventPresets.find((p) => p.id === selectedEvent.value) ?? eventPresets[0]\n})\n\nconst computedSignatureHex = computed(() => {\n  return generateDeterministicSignature(signingSecret.value, currentPayloadStr.value, currentTimestamp.value)\n})\n\nconst computedSignatureHeader = computed(() => {\n  if (signatureFormat.value === 'stripe') {\n    return `Stripe-Signature: t=${currentTimestamp.value},v1=${computedSignatureHex.value}`\n  }\n  return `webhook-signature: v1,${btoa(computedSignatureHex.value.slice(0, 32))}`\n})\n\nconst computedAllHeaders = computed(() => {\n  const t = currentTimestamp.value\n  const sigHex = computedSignatureHex.value\n  if (signatureFormat.value === 'stripe') {\n    return `POST ${endpointUrl.value}\\nHost: api.acme.dev\\nContent-Type: application/json\\nUser-Agent: UIPKGE-Webhooks/2.0\\nStripe-Signature: t=${t},v1=${sigHex}`\n  }\n  return `POST ${endpointUrl.value}\\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,${btoa(sigHex.slice(0, 32))}`\n})\n\nfunction handleEventChange(eventId: unknown) {\n  const id = String(eventId)\n  selectedEvent.value = id\n  const preset = eventPresets.find((p) => p.id === id)\n  if (preset) {\n    currentPayloadStr.value = JSON.stringify(preset.defaultPayload, null, 2)\n  }\n}\n\nfunction handleFormatJson() {\n  try {\n    const parsed = JSON.parse(currentPayloadStr.value)\n    currentPayloadStr.value = JSON.stringify(parsed, null, 2)\n  } catch {\n    // Keep as is if invalid\n  }\n}\n\nfunction handleResetPayload() {\n  const preset = eventPresets.find((p) => p.id === selectedEvent.value)\n  if (preset) {\n    currentPayloadStr.value = JSON.stringify(preset.defaultPayload, null, 2)\n  }\n}\n\nfunction handleRegenerateSecret() {\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  signingSecret.value = result\n}\n\nfunction handleCopy(text: string, key: string) {\n  navigator.clipboard?.writeText(text)\n  copiedKey.value = key\n  setTimeout(() => {\n    if (copiedKey.value === key) {\n      copiedKey.value = null\n    }\n  }, 2000)\n}\n\nfunction sendWebhook() {\n  if (isSending.value) return\n  isSending.value = true\n\n  const nowSec = Math.floor(Date.now() / 1000)\n  currentTimestamp.value = nowSec\n\n  setTimeout(() => {\n    let parsedBody: any = {}\n    let isInvalidJson = false\n    try {\n      parsedBody = JSON.parse(currentPayloadStr.value)\n    } catch {\n      isInvalidJson = true\n    }\n\n    const isFailureUrl = endpointUrl.value.includes('fail') || endpointUrl.value.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 ?? selectedEvent.value\n    const sig = `t=${nowSec},v1=${computedSignatureHex.value}`\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: endpointUrl.value,\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: currentPayloadStr.value,\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    deliveryHistory.value.unshift(newDelivery)\n    selectedDeliveryId.value = newDelivery.id\n    isSending.value = false\n  }, 450)\n}\n\nfunction retryDelivery(attempt: DeliveryAttempt) {\n  currentPayloadStr.value = attempt.requestBody\n  endpointUrl.value = attempt.endpointUrl\n  selectedEvent.value = attempt.eventType\n  sendWebhook()\n}\n\nfunction handleKeydown(e: KeyboardEvent) {\n  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {\n    e.preventDefault()\n    sendWebhook()\n  }\n}\n\nonMounted(() => {\n  window.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n  window.removeEventListener('keydown', handleKeydown)\n})\n</script>\n\n<template>\n  <div data-slot=\"webhook-tester\" :class=\"cn('w-full space-y-6', props.class)\">\n    <!-- Top Header Banner & Primary Endpoint Controls -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardHeader 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                <Zap class=\"size-4\" />\n              </div>\n              <CardTitle class=\"text-base font-semibold\">Webhook Event Simulator & Inspector</CardTitle>\n            </div>\n            <CardDescription class=\"text-xs\">\n              Trigger test events and verify signature headers against your endpoint.\n            </CardDescription>\n          </div>\n\n          <div class=\"flex items-center gap-2\">\n            <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\" />\n              Ingress Ready\n            </Badge>\n          </div>\n        </div>\n      </CardHeader>\n\n      <CardContent 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                <Globe class=\"size-3.5\" />\n                <span class=\"text-foreground font-semibold\">POST</span>\n              </span>\n              <Input\n                v-model=\"endpointUrl\"\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              size=\"default\"\n              class=\"w-full gap-2 text-xs font-medium shadow-xs\"\n              :disabled=\"isSending || !endpointUrl\"\n              @click=\"sendWebhook\"\n            >\n              <span\n                v-if=\"isSending\"\n                class=\"border-primary-foreground size-3.5 animate-spin rounded-full border-2 border-t-transparent\"\n              />\n              <Send v-else class=\"size-3.5\" />\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\">⌘↵</kbd>\n            </Button>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\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        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader 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                  <Layers class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">Event Configuration</CardTitle>\n                  <CardDescription class=\"text-xs\">Select event payload and adjust test parameters</CardDescription>\n                </div>\n              </div>\n              <Badge variant=\"secondary\" class=\"font-mono text-xs\">{{ activePreset.category }}</Badge>\n            </div>\n          </CardHeader>\n\n          <CardContent 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              <Select :model-value=\"selectedEvent\" @update:model-value=\"handleEventChange\">\n                <SelectTrigger class=\"font-mono text-xs\">\n                  <SelectValue placeholder=\"Select event preset\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem\n                    v-for=\"preset in eventPresets\"\n                    :key=\"preset.id\"\n                    :value=\"preset.id\"\n                    class=\"font-mono text-xs\"\n                  >\n                    {{ preset.name }}\n                  </SelectItem>\n                </SelectContent>\n              </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              <button\n                v-for=\"preset in eventPresets\"\n                :key=\"preset.id\"\n                type=\"button\"\n                :class=\"\n                  cn(\n                    'min-h-6 cursor-pointer rounded border px-2 py-0.5 font-mono text-xs transition-colors',\n                    selectedEvent === preset.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                @click=\"handleEventChange(preset.id)\"\n              >\n                {{ preset.id }}\n              </button>\n            </div>\n\n            <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                  <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                    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                    variant=\"ghost\"\n                    size=\"sm\"\n                    class=\"text-muted-foreground hover:text-foreground h-7 px-2 text-xs\"\n                    @click=\"handleResetPayload\"\n                  >\n                    <RefreshCw class=\"mr-1 size-3\" />\n                    Reset\n                  </Button>\n                  <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                    <Check v-if=\"copiedKey === 'payload'\" class=\"text-success mr-1 size-3\" />\n                    <Copy v-else class=\"mr-1 size-3\" />\n                    <span>{{ copiedKey === 'payload' ? 'Copied' : 'Copy' }}</span>\n                  </Button>\n                </div>\n              </div>\n\n              <Textarea\n                v-model=\"currentPayloadStr\"\n                :rows=\"11\"\n                no-resize\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          </CardContent>\n\n          <CardFooter\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          </CardFooter>\n        </Card>\n\n        <!-- Signing Secret & Signature Verification Preview Card -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader 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                  <ShieldCheck class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">Signature & Security Headers</CardTitle>\n                  <CardDescription class=\"text-xs\"\n                    >HMAC-SHA256 signature calculated from secret & timestamp</CardDescription\n                  >\n                </div>\n              </div>\n              <div class=\"flex items-center gap-1\">\n                <button\n                  type=\"button\"\n                  :class=\"\n                    cn(\n                      'min-h-6 cursor-pointer rounded px-2 py-0.5 font-mono text-xs transition-colors',\n                      signatureFormat === 'stripe'\n                        ? 'bg-primary text-primary-foreground font-medium'\n                        : 'bg-muted text-muted-foreground hover:text-foreground',\n                    )\n                  \"\n                  @click=\"signatureFormat = 'stripe'\"\n                >\n                  Stripe\n                </button>\n                <button\n                  type=\"button\"\n                  :class=\"\n                    cn(\n                      'min-h-6 cursor-pointer rounded px-2 py-0.5 font-mono text-xs transition-colors',\n                      signatureFormat === 'svix'\n                        ? 'bg-primary text-primary-foreground font-medium'\n                        : 'bg-muted text-muted-foreground hover:text-foreground',\n                    )\n                  \"\n                  @click=\"signatureFormat = 'svix'\"\n                >\n                  Svix Standard\n                </button>\n              </div>\n            </div>\n          </CardHeader>\n\n          <CardContent 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                  <RotateCw class=\"size-3\" />\n                  <span>Regenerate</span>\n                </button>\n              </div>\n              <div class=\"relative flex items-center\">\n                <Key class=\"text-muted-foreground pointer-events-none absolute left-3 size-3.5\" />\n                <Input v-model=\"signingSecret\" class=\"pr-16 pl-8 font-mono text-xs\" placeholder=\"whsec_...\" />\n                <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                  <Check v-if=\"copiedKey === 'secret'\" class=\"text-success size-3\" />\n                  <Copy v-else class=\"size-3\" />\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                  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                  <Check v-if=\"copiedKey === 'headers'\" class=\"text-success mr-1 size-3\" />\n                  <Copy v-else class=\"mr-1 size-3\" />\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          </CardContent>\n        </Card>\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        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader 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                  <Activity class=\"size-4\" />\n                </div>\n                <div>\n                  <CardTitle class=\"text-sm font-semibold\">Delivery History</CardTitle>\n                  <CardDescription class=\"text-xs\">Recent dispatch logs and response telemetry</CardDescription>\n                </div>\n              </div>\n              <span class=\"text-muted-foreground font-mono text-xs\">{{ deliveryHistory.length }} events logged</span>\n            </div>\n          </CardHeader>\n\n          <CardContent class=\"p-0\">\n            <div class=\"divide-border max-h-[250px] divide-y overflow-y-auto\">\n              <div\n                v-for=\"attempt in deliveryHistory\"\n                :key=\"attempt.id\"\n                :class=\"\n                  cn(\n                    'flex cursor-pointer items-center justify-between gap-3 p-3.5 text-xs transition-colors',\n                    selectedDeliveryId === attempt.id ? 'bg-muted/80 font-medium' : 'hover:bg-muted/40',\n                  )\n                \"\n                @click=\"selectedDeliveryId = attempt.id\"\n              >\n                <div class=\"flex min-w-0 items-center gap-2.5\">\n                  <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                  </Badge>\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                  <ArrowRight class=\"text-muted-foreground size-3.5\" />\n                </div>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n\n        <!-- Active Delivery Detail Inspector -->\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader 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                  <Badge :variant=\"activeDelivery.status === 200 ? 'success' : 'destructive'\" class=\"font-mono text-xs\">\n                    {{ activeDelivery.status }} {{ activeDelivery.statusText }}\n                  </Badge>\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                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-8 gap-1.5 text-xs font-medium\"\n                  @click=\"retryDelivery(activeDelivery)\"\n                >\n                  <RotateCw class=\"size-3.5\" />\n                  <span>Retry</span>\n                </Button>\n              </div>\n            </div>\n          </CardHeader>\n\n          <CardContent class=\"pt-0\">\n            <Tabs v-model=\"activeInspectorTab\" class=\"w-full\">\n              <TabsList class=\"grid w-full grid-cols-3\">\n                <TabsTrigger value=\"request\" class=\"text-xs\">Request</TabsTrigger>\n                <TabsTrigger value=\"response\" class=\"text-xs\">Response</TabsTrigger>\n                <TabsTrigger value=\"timings\" class=\"text-xs\">Diagnostics</TabsTrigger>\n              </TabsList>\n\n              <!-- Request Tab -->\n              <TabsContent 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                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                      @click=\"handleCopy(JSON.stringify(activeDelivery.requestHeaders, null, 2), 'req-headers')\"\n                    >\n                      <Check v-if=\"copiedKey === 'req-headers'\" class=\"text-success mr-1 size-3\" />\n                      <Copy v-else class=\"mr-1 size-3\" />\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>\n                      <TableHeader>\n                        <TableRow>\n                          <TableHead class=\"h-8 text-xs font-medium\">Header</TableHead>\n                          <TableHead class=\"h-8 text-xs font-medium\">Value</TableHead>\n                        </TableRow>\n                      </TableHeader>\n                      <TableBody>\n                        <TableRow v-for=\"(val, key) in activeDelivery.requestHeaders\" :key=\"key\">\n                          <TableCell class=\"text-muted-foreground py-1.5 font-mono text-xs\">{{ key }}</TableCell>\n                          <TableCell class=\"text-foreground max-w-[200px] truncate py-1.5 font-mono text-xs\">{{\n                            val\n                          }}</TableCell>\n                        </TableRow>\n                      </TableBody>\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                      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                      <Check v-if=\"copiedKey === 'req-body'\" class=\"text-success mr-1 size-3\" />\n                      <Copy v-else class=\"mr-1 size-3\" />\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              </TabsContent>\n\n              <!-- Response Tab -->\n              <TabsContent 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                      variant=\"ghost\"\n                      size=\"sm\"\n                      class=\"text-muted-foreground hover:text-foreground h-6 px-2 text-xs\"\n                      @click=\"handleCopy(JSON.stringify(activeDelivery.responseHeaders, null, 2), 'res-headers')\"\n                    >\n                      <Check v-if=\"copiedKey === 'res-headers'\" class=\"text-success mr-1 size-3\" />\n                      <Copy v-else class=\"mr-1 size-3\" />\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>\n                      <TableHeader>\n                        <TableRow>\n                          <TableHead class=\"h-8 text-xs font-medium\">Header</TableHead>\n                          <TableHead class=\"h-8 text-xs font-medium\">Value</TableHead>\n                        </TableRow>\n                      </TableHeader>\n                      <TableBody>\n                        <TableRow v-for=\"(val, key) in activeDelivery.responseHeaders\" :key=\"key\">\n                          <TableCell class=\"text-muted-foreground py-1.5 font-mono text-xs\">{{ key }}</TableCell>\n                          <TableCell class=\"text-foreground max-w-[200px] truncate py-1.5 font-mono text-xs\">{{\n                            val\n                          }}</TableCell>\n                        </TableRow>\n                      </TableBody>\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                      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                      <Check v-if=\"copiedKey === 'res-body'\" class=\"text-success mr-1 size-3\" />\n                      <Copy v-else class=\"mr-1 size-3\" />\n                      <span>{{ copiedKey === 'res-body' ? 'Copied' : 'Copy' }}</span>\n                    </Button>\n                  </div>\n                  <pre\n                    :class=\"\n                      cn(\n                        'max-h-[220px] overflow-auto rounded-md border p-3 font-mono text-xs leading-relaxed',\n                        activeDelivery.status === 200\n                          ? 'border-border bg-muted/40 text-foreground'\n                          : 'border-destructive/30 bg-destructive/10 text-destructive',\n                      )\n                    \"\n                  ><code>{{ activeDelivery.responseBody }}</code></pre>\n                </div>\n              </TabsContent>\n\n              <!-- Diagnostics & Timeline Tab -->\n              <TabsContent 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\">{{ activeDelivery.timings.dns }}ms</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\">{{ activeDelivery.timings.tls }}ms</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\">{{ activeDelivery.timings.ttfb }}ms</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                      <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                      <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                      <CheckCircle2 class=\"size-3.5 shrink-0\" />\n                      <span>Timestamp within tolerance envelope (300s window)</span>\n                    </li>\n                  </ul>\n                </div>\n              </TabsContent>\n            </Tabs>\n          </CardContent>\n\n          <CardFooter\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              <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          </CardFooter>\n        </Card>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/WebhookTester.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/input.json",
    "https://uipkge.dev/r/vue/select.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/table.json",
    "https://uipkge.dev/r/vue/tabs.json",
    "https://uipkge.dev/r/vue/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"
  ]
}