Webhooks are a peer projection of Vadyl's canonical event and subscription surfaces. Outbound deliveries are durable rows with attempt history. Inbound receivers verify signatures before parsing and deduplicate through receipts.

Management routes

GET    /api/webhooks/endpoints
POST   /api/webhooks/endpoints
GET    /api/webhooks/endpoints/{id}
PUT    /api/webhooks/endpoints/{id}
DELETE /api/webhooks/endpoints/{id}
POST   /api/webhooks/endpoints/{id}/rotate-secret
GET    /api/webhooks/endpoints/{endpointId}/deliveries

GET    /api/webhooks/receivers
POST   /api/webhooks/receivers
GET    /api/webhooks/receivers/{id}
PUT    /api/webhooks/receivers/{id}
DELETE /api/webhooks/receivers/{id}
POST   /api/webhooks/inbound/{publicReceiverKey}
GET    /api/webhooks/diagnostics

Create an outbound endpoint

curl -X POST https://api.vadyl.app/v1/webhooks/endpoints \
  -H "Authorization: Bearer $VADYL_TOKEN" \
  -H "X-Vadyl-Tenant: acme" \
  -H "X-Vadyl-Project: billing" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/vadyl",
    "description": "Billing events",
    "filter": { "op": "startsWith", "path": "$.type", "value": "order." },
    "retryPolicy": { "maxAttempts": 10, "baseDelaySeconds": 30 },
    "signingSecretRef": "secret:WEBHOOK_SIGNING_KEY"
  }'

Signature verification

The signature covers the exact raw bytes delivered to your server. Verify before JSON parsing. The canonical header format is t=unixTimestamp,v1=hexHmacSha256.

const payload = await request.arrayBuffer();
const header = request.headers.get("Vadyl-Signature");
const expected = hmacSha256(secret, timestamp + "." + rawBytes(payload));
timingSafeEqual(expected, signatureFrom(header));

Outbound delivery states

StateMeaning
PendingDelivery row is eligible for claim.
ClaimedA scanner owns this attempt under lease.
SucceededReceiver returned a configured success status.
RetryScheduledAttempt failed and next delivery time is scheduled with jitter.
DeadLetteredRetry policy exhausted or permanent failure classification.

Inbound receiver

curl -X POST https://api.vadyl.app/v1/webhooks/inbound/rcv_01HXZ \
  -H "Vadyl-Signature: t=1778157600,v1=4e7..." \
  -H "Content-Type: application/json" \
  -d '{ "type": "stripe.charge.succeeded", "id": "evt_external_123", "data": { "charge": "ch_123" } }'

Replay and diagnostics

POST /api/webhooks/deliveries/{deliveryId}/replay
GET  /api/webhooks/endpoints/{endpointId}/deliveries
GET  /api/webhooks/diagnostics

Replay is idempotent: a delivery attempt is new, but the canonical event identity and receiver idempotency semantics remain stable.