Skip to main content
A webhook tells your server what happened without polling: a code was entered, a code expired, a WhatsApp message was delivered or read, an e-mail bounced. Each application holds up to 5 endpoints, set in the console. Each endpoint has its own URL, its own signing secret and the events it chooses, grouped by product, so your e-mail events can go to one server and your WhatsApp events to another.

Set it up

Who can: the owner, an admin, or a developer whose access covers the whole application.
1

Add an endpoint

Open the application, then Application settings → Webhooks (each product’s menu has Webhooks too: the same page, opened on that product’s events). With no endpoint yet the page is the form itself. Enter the Endpoint URL: it must be https:// on a public host. A redirect is never followed and counts as a failed attempt, so give the final address.
2

Choose its events

The events are grouped by the products the application uses: Verify, WhatsApp and Email. Every one of them is ticked for a new endpoint; untick what this endpoint should not receive. An endpoint receives only the events it chose.
3

Keep its signing secret

Adding the endpoint creates its Signing secret and shows it once. Reveal shows it again for that page load, and every reveal is recorded in the account’s audit log. Store it beside your key, on your server only.
4

Send a test event

Send a test event on the endpoint asks which of its events to send, and delivers a sample of that event to its URL. It appears in the Delivery log below with your server’s answer. See Test events.
Most applications need one endpoint, and the page shows it as one webhook. Add another endpoint sends some events to a second server; with two or more, each endpoint is a card with Edit. A change of URL or events keeps the endpoint’s secret. Switch Receive events off to keep an endpoint without sending it anything, or Delete it. Rotate secret issues a new secret for that endpoint alone, and its old one stops verifying at once, so update your server right after.

Events

An event goes to every endpoint that is on and chose it, as one delivery each, signed with that endpoint’s secret and retried on its own. An event no endpoint chose is not sent. The event catalog has each event’s payload and when exactly it fires.

The envelope

Every delivery is one JSON object: the event, its data, and sent_at.
sent_at is the time of this attempt in milliseconds since the epoch, as a string, and equals the tawked-timestamp header. A retry carries a new sent_at and the same data. WhatsApp events carry the message’s fields under data instead. See message events. E-mail events carry the e-mail’s fields with the full address, and for a message of several recipients recipient and tags too: one event per recipient. Email, domain and suppression events add type (the same as event) and timestamp (when it happened) after data. See email events.

Verifying the signature

Every delivery carries these headers: Compute the HMAC over the raw request body, before any JSON parsing, compare it in constant time, and refuse old timestamps to close the replay window:
A signature that does not match usually means the body was parsed and re-serialised before hashing, the secret was rotated, or the server checks one endpoint’s delivery with another endpoint’s secret. Hash the bytes you received, with the secret of the endpoint whose URL received them.

Standard Webhooks signing

An endpoint can also be signed the Standard Webhooks way, which most webhook libraries verify. It is on for every endpoint created from now, and an older endpoint gets it when its owner turns it on: Turn on beside Standard Webhooks under Signing and test events on the Webhooks page, or PATCH /v1/webhooks/{id} with {"signing": {"standard_webhooks": true}}. Its own secret starts with whsec_ and is shown once; give it to a Standard Webhooks library. Every delivery keeps the tawked-* headers and adds: A worked example, with the secret whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw, the id 0199f220-5f69-7a10-9b2c-3d4e5f6a7b8c and the timestamp 1792746004, over this body:
webhook-signature: v1,2p1yE/bOP8DYox908cU0kx3dpX1ahAr4xjnX87X9Ah8=. The Tawked signature of the same body with the secret whk_test_7a1c2f9e0b3d4c5a6e7f8091a2b3c4d5 and tawked-timestamp: 1792746004380 is 122f13d49aec148582e9fc9d197c79b9a716fc57f2ba010f667a72db98a9df56. Rotating. Rotate the secret beside Standard Webhooks, or POST /v1/webhooks/{id}/rotate-secret (whose default is standard), makes a new whsec_ secret; the previous one keeps signing beside it for 24 hours, so your server moves to the new one without a rejected delivery. The Tawked secret rotates only when you ask for it ("scheme": "tawked" or "both"), and at once: there is no grace for it, since tawked-signature holds one value. Tawked’s SDKs (JavaScript, PHP, Python) and the Laravel middleware verify both schemes, and the secrets you give them choose which one, never the headers that arrive:
  • The Tawked secret alone: they check tawked-signature, whether Standard Webhooks signing is on or not. Nothing changes for a server that keeps only this secret.
  • The whsec_ secret (alone, or beside the Tawked secret): they check webhook-signature alone. A delivery whose Standard Webhooks signature fails is refused, even when its tawked-signature is valid; there is no fallback from one scheme to the other.
Give a server the secrets of the endpoint it serves. If you turn Standard Webhooks signing off on an endpoint, remove its whsec_ secret from the server first. During a rotation, give both whsec_ secrets.

Answer fast

Answer any 2xx within 5 seconds. Anything else is a failed attempt: another status, a timeout, a connection that fails, a redirect, or an address that is not public. Acknowledge first and do your own work after, from a queue.

Retries

A failed delivery is tried again: 5 attempts in all, right away and then after 1 minute, 5 minutes, 30 minutes and 2 hours. Each wait counts from the attempt before it. An endpoint that receives Email events is retried longer: 8 attempts over about 27.6 hours, after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours. When every delivery to it has failed for a day, its owner is told; five days after its first failed attempt, with at least three deliveries given up, Tawked switches it off and says so (Tawked’s rule), when it receives Email events only: one that also receives Verify or WhatsApp events is only warned, never switched off. Turn it on again from its card, or with PATCH /v1/webhooks/{id} and {"enabled": true}, once the server is fixed. An endpoint that receives only Verify or WhatsApp events keeps the schedule above and is never switched off. After the fifth failure the delivery is Failed and nothing more is sent on its own. The Delivery log shows each delivery’s status, attempts, your server’s last answer, the next attempt while one is scheduled, and What we sent. Fix your server, then open the delivery and Send again. The endpoint’s URL and secret are read at each attempt, so a fixed address or a rotated secret applies to deliveries still waiting. A delivery whose endpoint was deleted or switched off stops there and is never sent to another endpoint. The log shows each delivery’s endpoint and can be narrowed to one. Finished deliveries stay in the log for 90 days.

Duplicates and order

  • At least once. A delivery your server took but answered too slowly is sent again, and Send again creates a second delivery of the same event. Make your handler idempotent on the tawked-webhook-id header: one id per event, the same on every retry and on a delivery sent again. A handler that dedupes on the body uses event, data.id and data.recipient.id when recipient is present (an e-mail of several recipients sends one event per recipient), else event and data.id.
  • No order. Each delivery is sent and retried on its own, so message.read can arrive before message.delivered, and a retried event after a newer one. Compare the status in data with what you stored, and never move a record backwards.
  • Not every step. A message status can skip a step: a message read before its delivery was reported announces message.read alone. The final statuses are read and failed.

Test events

Send a test event on an endpoint delivers one sample of the event you choose from its events, signed with its secret like any other. Its envelope carries "test": true beside event and data, and its data has the fields of the real event with your application’s id and made-up values: the id is all zeros, the reference is test-event, a phone number is all zeros, an e-mail address is on example.com, and a code’s mode is test. No real event carries the test key, so treat an envelope that has it as a rehearsal and skip your own side effects.

Test mode events

Events of messages sent with a test key reach only the endpoints whose Receives test events switch is on (off by default; "receives_test": true by API), with "mode": "test" in the body. They never count toward an endpoint’s failures.

Sending again

A delivery can be sent again from its details in the log: Send again queues a new delivery with the same event, the same data and the same tawked-webhook-id to the same endpoint, at the URL it has now, with its own attempts. Delivered deliveries can be sent again too; your server may ignore one it already processed. By API, POST /v1/webhooks/{id}/deliveries/{delivery}/replay sends one again, and POST /v1/webhooks/{id}/replay sends again up to 1,000 deliveries of a window of at most 7 days within the last 30, the failed ones by default.

Manage endpoints by API

A live key with the explicit webhooks:manage manages the application’s endpoints: GET and POST /v1/webhooks, GET, PATCH and DELETE /v1/webhooks/{id}, POST /v1/webhooks/{id}/rotate-secret and /test, the deliveries under /v1/webhooks/{id}/deliveries, and the replays. A full-access key does not hold webhooks:manage: choose it on the key. Creating an endpoint answers its two secrets once. The API reference has every call, under Webhooks.

Partner applications

A partner’s client applications have no endpoints of their own. Their verification.* events reach the partner webhook instead, with the partner’s external_id in the data, once the partner opts in. The signature, the timeout and the retry schedule are the same.
Last modified on October 29, 2026