Channelsadvanced

Subscribe to webhook events

Create a webhook subscription with POST /api/v1/webhooks. You pick the events you want, give Atender a URL, and your server starts receiving signed event payloads.

10 min read

Subscribe to webhook events

Webhook subscriptions let Atender notify your server when something happens — a new message arrived, a conversation was resolved, an SLA was breached. You can create and manage subscriptions in Settings → Integrations → Webhooks, or automate them with the v1 API.

Before you start

  • To manage subscriptions in the UI, go to Settings → Integrations → Webhooks.
  • To manage subscriptions with the API, use a tenant API key with webhooks:write scope. Generate one in Settings → API & MCP.
  • If you only need to list subscriptions with the API, use a tenant API key with webhooks:read scope.
  • A public HTTPS endpoint on your server that can receive POST requests and respond quickly (under 10 seconds).
  • A way to store the webhook secret Atender returns when you create or rotate the subscription — you’ll need it to verify signatures.

Create a subscription

Use Settings → Integrations → Webhooks to create a subscription without writing code. The settings screen can create, edit, pause, start, re-enable, and delete subscriptions. It can also rotate the signing secret. The secret is shown only when you create a subscription or rotate its secret — store it immediately because you cannot retrieve it later.

For automation, create a subscription with the API:

POST https://YOUR-ATENDER-HOST/api/v1/webhooks

Headers:

Authorization: Bearer sa_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

Body:

{
  "name": "production-listener",
  "url": "https://your-server.example.com/atender-webhooks",
  "events": [
    "message.received",
    "message.sent",
    "conversation.resolved"
  ]
}

Response (201 Created). Every v1 response wraps its payload in a data object — read body.data, not body:

{
  "data": {
    "id": "...",
    "name": "production-listener",
    "url": "https://your-server.example.com/atender-webhooks",
    "secret": "whsec_...",
    "events": ["message.received", "message.sent", "conversation.resolved"],
    "isActive": true,
    "customHeaders": {},
    "createdAt": "2026-05-11T09:00:00.000Z"
  }
}

The secret is returned only at creation time. Store it immediately — you cannot retrieve it later. If you lose it, rotate it in Settings → Integrations → Webhooks or with POST /api/v1/webhooks/:id/rotate-secret.

What your server receives

When a subscribed event fires, Atender POSTs your URL with a JSON envelope:

{
  "event": "message.received",
  "eventId": "5f0c2a1e-7d4b-4c9a-9a1f-3e8b6d2c4f10",
  "deliveredAt": "2026-05-11T09:00:00.000Z",
  "payload": { ... }
}

The exact payload shape depends on the event — see Webhook events reference.

Every delivery carries these headers:

Content-Type: application/json
User-Agent: Atender-Webhooks/1.0
X-Webhook-Event: message.received
X-Webhook-Delivery: 5f0c2a1e-7d4b-4c9a-9a1f-3e8b6d2c4f10
X-Webhook-Signature: sha256=<hex digest>

X-Webhook-Delivery is a unique delivery UUID (the same value as eventId in the body) — use it to deduplicate.

Verify signatures

Every webhook delivery is signed using the subscription secret. Verify the signature before trusting the payload — otherwise anyone who knows your endpoint can forge events.

The signature is sent in the X-Webhook-Signature header as sha256=<hex digest>. Compute the HMAC-SHA256 of the raw request body using your subscription’s secret (the whsec_... value) as the key, hex-encode it, and compare it to the header value after the sha256= prefix. If it matches, the request is genuine.

Respond quickly

Your endpoint must respond within 10 seconds. The expected response is 2xx. Each event is delivered once — there is no automatic retry today, so treat webhooks as notifications and refetch authoritative state via the API rather than relying on every delivery arriving. (maxRetries and retryDelaySeconds are not accepted on the subscription body — sending either one returns a 400 that names the field.)

After 10 consecutive failed deliveries, the subscription is disabled automatically — you can see why under disableReason when you GET the subscription.

Best practice: acknowledge the delivery immediately with a 200 OK, then process the event in a background job. Don’t do heavy work in the webhook handler itself.

List, update, delete

  • GET — /api/v1/webhooks — List your tenant’s subscriptions. Includes delivery status and consecutive-failure count.
  • GET — /api/v1/webhooks/:id — Get full details for one subscription, including custom headers, delivery status and disable reason.
  • PATCH — /api/v1/webhooks/:id — Update name, URL, event list, active/paused state, custom headers.
  • DELETE — /api/v1/webhooks/:id — Permanently remove a subscription.
  • POST — /api/v1/webhooks/:id/rotate-secret — Generate a new signing secret. Returns the new secret in the response — old secret immediately stops working.

All require webhooks:write except GET which requires webhooks:read.

Pause vs disable

A paused subscription receives no deliveries but remains in your tenant — useful for temporary maintenance or debugging.

A disabled subscription was paused automatically by Atender after too many consecutive failures. Check disableReason to understand why, fix your endpoint, then PATCH isActive: true to re-enable.

Limits

  • Each tenant has a maxWebhooks limit (5 by default, adjustable per tenant). Trying to create more returns 403 LIMIT_EXCEEDED.

Troubleshooting

  • Symptom: POST /api/v1/webhooks returns 403. Fix: Your API key lacks the webhooks:write scope, or you’ve hit the maxWebhooks limit. Check the response body’s error message (LIMIT_EXCEEDED vs a scope error) to tell which.

  • Symptom: Subscription created but no events arrive. Fix: Confirm the subscription is isActive: true and not isPaused. Check lastDeliveryAt and lastDeliveryStatus to see whether Atender tried and failed, or whether nothing triggered yet.

  • Symptom: The subscription got disabled. Fix: GET it and read disableReason — typically “auto-disabled after 10 consecutive failures”. Fix your endpoint, then PATCH isActive: true to re-enable.

  • Symptom: Signature verification fails. Fix: Make sure you’re computing HMAC-SHA256 over the raw request body, not the parsed JSON. Many web frameworks parse the body before you see it — most have a way to retain the raw bytes.

See also

Tags

How To