Webhooks

poolse POSTs JSON to a URL on your backend when something happens in your tenant. Every endpoint here takes an API key with the webhooks:admin scope. Without it you get 403 forbidden. You can also manage webhooks on the dashboard; see Webhooks.

The webhook object

{
  "id": "7d3b9f2e-0a4c-4e6b-8d1f-3c5a7e9b1d24",
  "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
  "name": "main",
  "url": "https://example.com/webhooks/poolse",
  "events": ["message.created", "call.finished"],
  "is_active": true,
  "last_delivery_at": "2026-06-02T08:13:56.123456Z",
  "inserted_at": "2026-06-01T10:00:00.000000Z",
  "updated_at": "2026-06-01T10:00:00.000000Z"
}
FieldTypeDescription
namestring1–255 characters.
urlstringWhere deliveries go. Must be an http or https URL with a host.
eventsstring[]The events to send. An empty list sends every event.
is_activebooleanfalse pauses the webhook: no new deliveries are created.
last_delivery_atstring | nullTime of the last successful (2xx) delivery.

The signing secret is never part of this object.

Create a webhook

POST/v1/webhooks
API keywebhooks:admin
FieldTypeRequiredDescription
namestringyes
urlstringyes
eventsstring[]yesEvent names from Events. Send [] for every event.
is_activebooleannoDefault true.

Response

201:

{
  "webhook": {
    "id": "7d3b9f2e-0a4c-4e6b-8d1f-3c5a7e9b1d24",
    "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
    "name": "main",
    "url": "https://example.com/webhooks/poolse",
    "events": [],
    "is_active": true,
    "last_delivery_at": null,
    "inserted_at": "2026-06-01T10:00:00.000000Z",
    "updated_at": "2026-06-01T10:00:00.000000Z"
  },
  "signing_secret": "q3Jx0b8m1F6Z…="
}
curl -X POST https://api.poolse.dev/v1/webhooks \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"main","url":"https://example.com/webhooks/poolse","events":["message.created"]}'

Errors

CodeStatusWhen
validation_failed422A field is missing or invalid, or an event name is unknown. details names the field.
webhooks_not_in_plan402Your plan does not include webhooks.
webhook_count_cap_exceeded402You have as many webhooks as your plan allows. details: { count, cap }.
subscription_inactive402Your subscription is past due or cancelled.
create_failed422Any other failure.

List webhooks

GET/v1/webhooks
API keywebhooks:admin

200: { "data": [ Webhook, … ] }, newest first. Not paginated.

Update a webhook

PATCH/v1/webhooks/:id
API keywebhooks:admin

Send any of name, url, events, is_active. The signing secret cannot be changed.

curl -X PATCH https://api.poolse.dev/v1/webhooks/$WEBHOOK_ID \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active":false}'

200: the updated webhook.

CodeStatusWhen
not_found404No such webhook in the tenant.
validation_failed422A field is invalid.

Delete a webhook

DELETE/v1/webhooks/:id
API keywebhooks:admin

Deletes the webhook and its delivery history. Deliveries not yet sent are dropped. 204 on success. 404 not_found if there is no such webhook.


Deliveries

Each event sent to a webhook is one delivery, retried until it succeeds or runs out of attempts.

The delivery object

{
  "id": "b81e4c0a-9d2f-4a6b-8c3e-7f1d5a9b2e60",
  "webhook_id": "7d3b9f2e-0a4c-4e6b-8d1f-3c5a7e9b1d24",
  "event": "message.created",
  "payload": { "type": "message.created", "tenant_id": "65787825-…", "data": { } },
  "attempt": 1,
  "response_status": 200,
  "response_body": "ok",
  "last_error": null,
  "next_attempt_at": null,
  "delivered_at": "2026-06-02T08:13:56.123456Z",
  "failed_at": null,
  "inserted_at": "2026-06-02T08:13:55.123456Z",
  "updated_at": "2026-06-02T08:13:56.123456Z"
}
FieldTypeDescription
eventstringThe event name.
payloadobjectThe stored body without id and created_at, which are added at send time.
attemptintegerAttempts made so far.
response_status, response_bodyinteger, string | nullFrom the latest attempt.
last_errorstring | nullWhy the latest attempt failed, for example HTTP 500.
next_attempt_atstring | nullWhen the next retry is due.
delivered_atstring | nullSet when a 2xx came back.
failed_atstring | nullSet when every attempt failed. No more retries happen.

List deliveries

GET/v1/webhooks/:id/deliveries
API keywebhooks:admin

200: { "data": [ Delivery, … ] }, the 50 most recent, newest first. There is no pagination and no way to read older deliveries. Deliveries are kept for 30 days. 404 not_found if there is no such webhook.

Replay a delivery

POST/v1/webhooks/:id/deliveries/:delivery_id/replay
API keywebhooks:admin

Sends the same event again as a new delivery, with a new id. The original delivery is not changed. A replay is sent even if the webhook is paused (is_active: false) and even when your daily delivery quota is used up. It does count toward the quota.

201: the new delivery, not yet attempted. Watch the deliveries list for the result.

CodeStatusWhen
not_found404No such webhook, or no such delivery on it.
replay_failed422The new delivery could not be created.

What poolse sends

An HTTP POST with a JSON body:

{
  "id": "b81e4c0a-9d2f-4a6b-8c3e-7f1d5a9b2e60",
  "type": "message.created",
  "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
  "created_at": "2026-06-02T08:13:55.123456Z",
  "data": { }
}

id is the delivery id and stays the same across retries: use it to skip duplicates. A replay has a new id. data depends on the event: see Webhook events.

Headers:

HeaderValue
content-typeapplication/json
user-agentpoolse-webhook/1
x-poolse-signaturet=<unix seconds>,v1=<hex HMAC>
x-poolse-event-idThe delivery id.
x-poolse-event-typeThe event name.
x-poolse-delivery-attempt1 for the first attempt, 2 for the first retry, and so on.

Verify the signature. v1 is the lowercase hex HMAC-SHA256 of <t>.<raw body>, keyed with the signing secret exactly as returned at creation. Compute it over the raw bytes you received, compare in constant time, and reject old t values. Code is in Webhook verification.

Retries

  • A 2xx response marks the delivery delivered.
  • Any other status, a timeout or a network error is a failure. poolse retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours: 6 attempts in all. After the sixth failure the delivery is marked failed and is not retried unless you replay it.

When events are dropped

poolse does not create a delivery, and does not queue it for later, when:

  • your plan's daily delivery quota is used up (a rolling 24 hours);
  • your tenant produces more than 1,000 webhook events in a minute (an abuse limit, the same on every plan);
  • the webhook is paused.

Nothing tells your endpoint about a dropped event. See Limits & quotas for plan quotas.

Events

EventSent when
message.createdA message is sent.
message.updatedA message is edited.
message.deletedA message is deleted.
mention.createdA message mentions a user. One event per mentioned user.
conversation.createdA conversation is created.
conversation.updatedA conversation is updated with PATCH /v1/conversations/:id.
member.addedA user is added to a conversation.
member.removedA user leaves or is removed.
call.startedA call is placed or a room opened.
call.answeredThe first person rung picks up. Calls only.
call.finishedA call or room ends.

The data of each event is documented in Webhook events.

Webhooks · API · poolse docs