Skip to main content
All endpoints accept and return JSON, require a PAT in Authorization: Bearer <token>, and accept Content-Type: application/json. Base URL: https://api.keebai.com/v1.
MethodPathScope
POST/v1/webhookswebhooks:manage
GET/v1/webhookswebhooks:read
GET/v1/webhooks/:idwebhooks:read
PATCH/v1/webhooks/:idwebhooks:manage
POST/v1/webhooks/:id/rotate-secretwebhooks:manage
DELETE/v1/webhooks/:idwebhooks:manage
POST/v1/webhooks/:id/testwebhooks:manage
GET/v1/webhooks/:id/deliverieswebhooks:read

Create a subscription

POST /v1/webhooks — returns the raw secret only once.
curl -X POST https://api.keebai.com/v1/webhooks \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production CRM",
    "url": "https://hooks.example.com/keebai",
    "events": ["whatsapp.message.received", "whatsapp.channel.connected"],
    "headers": { "X-Tenant-Origin": "crm" },
    "is_active": true
  }'
Body
FieldTypeRequiredValidation
namestringyes3–120 chars.
urlstringyesValid HTTPS URL, ≤2048 chars. http:// is rejected.
eventsstring[]yes≥1 event, unique, must belong to the catalog.
headersobjectnoExtra headers. string → string map.
is_activebooleannoDefault true.
Response 201
{
  "id": "wh_01HXYZ...",
  "name": "Production CRM",
  "url": "https://hooks.example.com/keebai",
  "events": ["whatsapp.message.received", "whatsapp.channel.connected"],
  "headers": { "X-Tenant-Origin": "crm" },
  "is_active": true,
  "secret_prefix": "whsec_8f2c5b3a",
  "secret": "whsec_8f2c5b3a9d4e7c1f...",
  "created_at": "2026-04-27T12:34:56.789Z",
  "updated_at": "2026-04-27T12:34:56.789Z"
}
secret is returned only in this response. You will never see it again. Store it in your secret manager. If you lose it, you have to rotate (POST /v1/webhooks/:id/rotate-secret), which invalidates the old one.

List subscriptions

GET /v1/webhooks — returns every subscription on your company.
curl https://api.keebai.com/v1/webhooks \
  -H "Authorization: Bearer kbai_pk_xxx"
Response 200
{
  "data": [
    {
      "id": "wh_01HXYZ...",
      "name": "Production CRM",
      "url": "https://hooks.example.com/keebai",
      "events": ["whatsapp.message.received"],
      "is_active": true,
      "secret_prefix": "whsec_8f2c5b3a",
      "failure_count": 0,
      "disabled_reason": null,
      "last_delivery_at": "2026-04-27T13:00:00Z",
      "created_at": "2026-04-27T12:34:56Z",
      "updated_at": "2026-04-27T12:34:56Z"
    }
  ]
}
secret is not included in list or get-by-id. Only secret_prefix (the first 12 chars), which is enough to visually identify which key you’re using.

Get a subscription

GET /v1/webhooks/:id
curl https://api.keebai.com/v1/webhooks/wh_01HXYZ \
  -H "Authorization: Bearer kbai_pk_xxx"
Same shape as a list item.

Update a subscription

PATCH /v1/webhooks/:id — every field is optional.
curl -X PATCH https://api.keebai.com/v1/webhooks/wh_01HXYZ \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["whatsapp.message.received", "whatsapp.message.failed"],
    "is_active": true
  }'
Useful for:
  • Reactivating a subscription that was auto-disabled by excessive failures (is_active: true).
  • Adding or removing events without recreating it (preserves the secret).
  • Changing the URL (preserves the secret).

Rotate secret

POST /v1/webhooks/:id/rotate-secret
curl -X POST https://api.keebai.com/v1/webhooks/wh_01HXYZ/rotate-secret \
  -H "Authorization: Bearer kbai_pk_xxx"
Response 200 — same shape as create, with a new secret. The old one is invalidated immediately. For zero-downtime rotation, see security.

Dispatch a synthetic event (test)

POST /v1/webhooks/:id/test — sends a synthetic envelope with data._test: true to your URL.
curl -X POST https://api.keebai.com/v1/webhooks/wh_01HXYZ/test \
  -H "Authorization: Bearer kbai_pk_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "whatsapp.message.received" }'
Response 202
{
  "accepted": true,
  "event_id": "evt_01HXYZ_TEST..."
}
Useful in CI: chain a webhooks test after deploying your endpoint to verify that HMAC verification still works.

List deliveries

GET /v1/webhooks/:id/deliveries?limit=50&cursor=<delivery_id>
curl 'https://api.keebai.com/v1/webhooks/wh_01HXYZ/deliveries?limit=50' \
  -H "Authorization: Bearer kbai_pk_xxx"
Response 200
{
  "data": [
    {
      "id": "dlv_01HXYZ...",
      "event_id": "evt_01HXYZ...",
      "event_type": "whatsapp.message.received",
      "status": "success",
      "status_code": 200,
      "attempts": 1,
      "duration_ms": 142,
      "last_error": null,
      "delivered_at": "2026-04-27T13:00:00.142Z",
      "created_at": "2026-04-27T13:00:00.000Z"
    }
  ],
  "next_cursor": "dlv_..."
}
status:
  • pending — queued, not yet attempted.
  • success — 2xx response.
  • failed — non-retryable failure (4xx other than 408/429), or retries are exhausted but still in the retry window.
  • dead — exceeded the 5 attempts, no further retries.

Delete a subscription

DELETE /v1/webhooks/:id
curl -X DELETE https://api.keebai.com/v1/webhooks/wh_01HXYZ \
  -H "Authorization: Bearer kbai_pk_xxx"
Response 204 — no body. Future events are not sent to this URL.

Common errors

StatusCodeCause
401UNAUTHENTICATEDPAT is invalid, expired, or revoked.
403INSUFFICIENT_SCOPEThe PAT is missing webhooks:read or webhooks:manage for the operation.
400VALIDATION_ERRORInvalid body (non-HTTPS URL, event_type outside the catalog, name too short, etc.). The details field lists the violations.
404WEBHOOK_NOT_FOUNDId doesn’t exist or belongs to another company.
429RATE_LIMIT_EXCEEDEDPublic API throttling. Retry after Retry-After.

Auto-disable

If a subscription accumulates 50 consecutive dead deliveries, we mark it is_active: false with disabled_reason: 'excessive_failures' and email the owner. Reactivate via PATCH /v1/webhooks/:id { "is_active": true } once you’ve fixed the endpoint. This stops an endpoint under maintenance from collecting noise for hours. The counter resets on the next successful delivery.