The events
Everyservice.* event carries service_id and application_id (the same value; service_id is the legacy name) and your external_id.
When Tawked’s review rejects an application it picks a reason from a fixed list, and
service.rejected carries its code beside the sentence in reason. Branch on the code, never on the sentence, which may change wording:
A rejection you make yourself in the console, and one made before 2026-10-04, carry no
reason_code. A rejection that may be appealed can be, once, within 14 days, from your console’s Clients place; Tawked answers within two working days and the next service.approved or service.rejected tells you the outcome. The list may grow: treat an unknown code as other.
And, opt-in because they come one per code, the verification events a client’s own application would get on its own webhook, with your external_id added to the data:
A client application sends no WhatsApp, so there is no
message.* event for a partner.
Subscriptions
By default everyservice.* event and balance.low reach your webhook; the verification events do not until you opt in. Change the set in the console under Clients → API and webhook, Webhook, Edit events, or with PUT /v1/partner/webhook:
null resets the default set. GET /v1/partner/webhook shows the current set beside default_events and available_events. A subscription changes delivery only: lifecycle and balance events are written to the log whatever you subscribed to, while the opt-in events are written only when subscribed, since they are readable through the code log anyway.
Endpoints that existed before subscriptions did keep receiving the events they always received (
service.approved, service.rejected, service.suspended, service.paused, balance.low) until you opt in to more. Nothing new reaches an existing integration on its own.The feed
GET /v1/partner/events lists the log oldest first, up to 100 rows a page, each with the event, your external_id, the data the webhook would carry, and the delivery it produced when there was one.
- Resume with the cursor.
next_cursorpoints after the last row you received, whether or not more rows exist now; keep it and pass it back ascursoron the next call.has_moresays whether a page is waiting right now. An empty page answersnull, so keep the cursor you had. - Start from a time.
sincetakes an ISO 8601 date or datetime for the first call, when you have no cursor yet. - Filter with
event(one event name) andapplication(yourexternal_id). - Two seconds behind. Rows younger than two seconds are held back, so a cursor you save never skips a row whose sequence was taken earlier but committed later.
- At least once. Treat the feed like the webhook: dedupe by the event
id. - Ninety days. Events and finished deliveries older than 90 days are pruned.
data; a webhook delivery’s event header is the same name. Read the deliveries when you want to know whether a specific webhook call succeeded.