Skip to main content
Every event travels in the same envelope, { "event", "data", "sent_at" }, signed the same way. Webhooks covers the setup, the signature, the timeout and the retries; this page covers what each event means.

Index

Handling any event

  • Verify the signature first, over the raw body. See Verifying the signature.
  • Answer 2xx within 5 seconds, then do the work from a queue.
  • Deduplicate on data.id together with event. The same event can arrive twice.
  • Do not rely on order. Compare the status in data with what you stored, and never move a record backwards.
  • Skip test events. An envelope with "test": true is a rehearsal from the console’s Send a test event.
  • Ignore what you do not know. New events and new data fields can be added; an unknown event deserves a 2xx and nothing else.

Verification events

The data is the verification, in the same shape for the three events. A partner’s copy adds external_id. Codes sent with a test key fire these events too, with mode: "test". Canceling a verification fires no event.

When a code is verified

verification.verified fires on the check that matched the code, once per verification. A later check of the same id answers verified again but sends no second event.
Handle it by marking the action behind reference as confirmed, if your own check call has not done so already. Your server already knows the outcome from the check answer; the event matters when another service makes the check, or as a record.

When a verification fails

verification.failed fires when a wrong code uses the last attempt (max_attempts, 3 by default), or when a check finds the attempts already used. The code can no longer be verified.
A send that fails at start (502 send_failed) fires no event: the API answer is the news. Handle it by refusing the action behind reference. Repeated failures from one number or one address are a fraud signal worth logging.

When a code expires

verification.expired fires when a code reaches expires_at unused. A check, a GET /v1/verify/{id} or a cancel that finds the expiry fires it at once; otherwise a sweep finds it, every ten minutes, at least five minutes after the expiry. Expect it up to about a quarter of an hour late.
Handle it as a cleanup signal, never as the only timeout: your sign-in screen should count down from expires_at itself.

Message events

message.sent, message.delivered, message.read and message.failed follow a WhatsApp template message sent through Tawked Notifications, from the API or from the console. They reach the application’s own webhook only; a partner’s client applications send no WhatsApp.
How the events behave:
  • They arrive within about a minute of Meta reporting the status, and a message is followed for 48 hours after its send.
  • A status only moves forward: sent, then delivered, then read. A step can be skipped: a message read before its delivery was reported fires message.read alone.
  • read and failed are final. Nothing follows them.
  • A send refused at once answers 502 send_failed to the API call and fires no message.failed.
Handle them by storing the latest status per id and ignoring an event whose status is behind the stored one. Sending messages has the status lifecycle.

Partner events

The service.* events and balance.low reach a partner’s webhook only, for the client applications it provisions and for its balance. Each service.* event carries service_id, application_id and the partner’s external_id.
Partner events has each one with its extra fields, the reason_code of a rejection, the subscriptions and the feed to poll instead.
Last modified on October 6, 2026