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:
Exayard-Signature: t=<unix>,v1=<base64-hmac-sha256>Exayard-Event-Id,Exayard-Event-Type
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.