Incoming Webhooks

Incoming webhooks are provider-to-Kravos.ai callbacks. They let external systems deliver events to the web app so Kravos.ai can verify them, normalize them, ...

Incoming Webhooks

Incoming webhooks are provider-to-Kravos.ai callbacks. They let external systems deliver events to the web app so Kravos.ai can verify them, normalize them, queue work, and update the database.

Not outbound event subscriptions

This page covers inbound provider webhooks already implemented in the web app. Customer-managed outbound webhook event subscriptions are currently unavailable.

Implemented Webhook Endpoints

EndpointProviderMain purposeVerification implemented
GET /api/webhooks/whatsappMeta WhatsAppWebhook challengeMETA_VERIFY_TOKEN challenge check
POST /api/webhooks/whatsappMeta WhatsAppInbound channel messagesx-hub-signature-256 HMAC with META_APP_SECRET
GET /api/webhooks/messengerMeta MessengerWebhook challengeMETA_VERIFY_TOKEN challenge check
POST /api/webhooks/messengerMeta MessengerInbound messages and Page echoesx-hub-signature-256 HMAC with META_APP_SECRET
POST /api/webhooks/elevenlabsElevenLabsVoice session events and transcriptsElevenLabs SDK verification with ELEVENLABS_WEBHOOK_SECRET
POST /api/webhooks/paddlePaddleBilling, subscription, and credit eventsPaddle SDK verification with PADDLE_WEBHOOK_SECRET
POST /api/webhooks/sendgridSendGridEmail delivery log updatesPayload validation only

Verification Details

Meta challenge requests

WhatsApp and Messenger use the same GET challenge contract.

  • hub.mode must be subscribe.
  • hub.verify_token must equal META_VERIFY_TOKEN.
  • hub.challenge must be present.

Kravos.ai returns the challenge text directly on success. It returns 403 when the token does not match and 503 when the verify token is not configured.

Meta signed POST requests

WhatsApp and Messenger POST handlers:

  1. read the raw request body with request.text()
  2. read x-hub-signature-256
  3. calculate sha256= + HMAC SHA-256 of the raw body using META_APP_SECRET
  4. compare the expected and provided digests with a timing-safe comparison
  5. parse JSON only after signature verification passes

Invalid signatures return:

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid webhook signature"
  }
}

The implemented Meta handlers do not enforce a timestamp replay window. Replay safety comes from provider message ID dedupe and database uniqueness checks.

ElevenLabs signed POST requests

The ElevenLabs route reads the raw body and accepts either x-elevenlabs-signature or elevenlabs-signature. It passes the body, signature, and ELEVENLABS_WEBHOOK_SECRET to the ElevenLabs SDK constructor.

Invalid signatures return 401 UNAUTHORIZED. Invalid event shape returns 400 VALIDATION_ERROR.

Paddle signed POST requests

The Paddle route reads the raw body, reads paddle-signature, and calls the Paddle SDK unmarshal helper with PADDLE_WEBHOOK_SECRET.

Invalid signatures return 400 INVALID_SIGNATURE. Unknown Paddle event types are acknowledged with 200 so Paddle does not keep retrying events the app intentionally ignores.

SendGrid delivery events

The SendGrid route validates that the request body is an array of SendGrid event objects, then updates email logs. It does not verify a SendGrid signature, so treat it as a delivery-log integration endpoint, not a general untrusted ingestion endpoint.

Replay And Duplicate Safety

Provider/pathImplemented duplicate handling
Meta WhatsApp and MessengerRedis key dedupe:{channel}:{providerMessageId} for 24 hours before queuing.
Channel message persistenceDatabase uniqueness on conversationId + channelMessageId.
Channel outbound repliesRedis key channel:outbound:{outboundId} for 7 days before sending again.
ElevenLabsEvent ID hash plus Redis key webhook:elevenlabs:event:{eventId} for 24 hours; BullMQ job ID also uses the event ID.
PaddleHandlers check current database state and domain ledger uniqueness before writing.
SendGridDelivery log updates are applied per event; there is no route-level dedupe key.

Secrets And Safe Handling

  • Store webhook secrets in deployment secrets or environment variables, not in source control.
  • Use different verify tokens and app secrets per environment.
  • Rotate provider tokens when ownership changes or a secret may have leaked.
  • Never send provider app secrets to clients. The Meta verify token is not the app secret.
  • Preserve the raw request body for signature verification.
  • Do not log raw webhook payloads or authorization headers in production.

Retries, Failures, And Dead Letters

Provider retry behavior depends on the provider. Kravos.ai should return 2xx only after it verifies and accepts the event.

Implemented queue behavior:

QueueAttemptsBackoffRetention
channel-inbound5Exponential, starts at 2 seconds100 completed, 500 failed
channel-outbound3Exponential, starts at 2 seconds100 completed, 500 failed
voice-webhook5Exponential, starts at 2 seconds100 completed, 500 failed

There is no separate dead-letter queue. Failed jobs remain in BullMQ until the configured failed-job retention limit removes older entries. Worker failure events are logged, and final-failure admin email alerts can be sent when SendGrid email sending is configured.

Testing Incoming Webhooks

For Meta channels, prefer provider verification plus a real test message. If you need to test a signed POST manually, compute the signature over the exact body bytes you send.

Example signature shape:

x-hub-signature-256: sha256=<hex HMAC SHA-256 of raw body using META_APP_SECRET>

Expected success response for Meta channel events:

{
  "data": {
    "received": true,
    "deduped": false,
    "dedupedCount": 0,
    "enqueued": 1
  }
}

Troubleshooting

Verification succeeds but no message appears

  • Confirm Redis is reachable so the handler can claim the dedupe key.
  • Confirm the worker process is running.
  • Confirm channel-inbound jobs are not failing.
  • Confirm first-message routing has exactly one active tenant channel config for the channel.

Duplicate provider events appear

  • Confirm provider message IDs are present in the payload.
  • Confirm Redis is not flushing dedupe keys early.
  • Check whether the duplicate was delivered after the 24-hour Meta dedupe window.

Provider keeps retrying

  • Check whether the route is returning 401, 403, 400, or 503.
  • Fix signature secrets before retrying. A successful retry with the same provider message ID should dedupe safely.
  • For queued work failures, inspect BullMQ failed jobs rather than replaying the provider event blindly.

Channels

Understand channel routing, queue processing, and supported channel behavior.

WhatsApp Setup

Configure Meta WhatsApp Cloud API callbacks and replies.

Messenger Setup

Configure Facebook Messenger callbacks, Page replies, and echo events.

Last updated: August 2026