Subscribe to webhooks

1. Register an endpoint

curl -X POST https://api.exayard.com/v1/webhook_endpoints \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "org_...",
    "url": "https://yourapp.com/hooks/exayard",
    "events": ["assessment.completed", "estimate.generated", "bid.generated"]
  }'

The response includes a secret (shown once). Store it somewhere durable — you can't retrieve it again.

2. Verify signatures

Every delivery has two headers:

Compute HMAC-SHA256 over ${t}.${rawBody} with your endpoint secret. Reject deliveries where t is more than 5 minutes from now.

The SDK does this for you:

import { constructWebhookEvent, WebhookSignatureError } from '@exayard/sdk'

try {
  const event = await constructWebhookEvent(
    rawBody,
    req.headers['exayard-signature'] as string,
    process.env.EXAYARD_WEBHOOK_SECRET!
  )
  switch (event.type) {
    case 'assessment.completed': /* ... */ break
    case 'estimate.generated':   /* ... */ break
  }
} catch (err) {
  if (err instanceof WebhookSignatureError) return res.status(400).send(err.code)
  throw err
}

3. Handle retries

We retry on any non-2xx response (or network error) with exponential backoff + jitter: 0s, 15s, 1m, 5m, 30m, 2h, 6h, 24h. After 8 attempts the delivery is marked failed and surfaces in GET /v1/webhook_endpoints/{id}/deliveries.

Use the Exayard-Event-Id for consumer-side deduplication — same id means same delivery.