Guide to tawk.to Webhooks: Features and Best Practices
tawk.to webhooks tell your systems what is happening in your live chat and help desk: a visitor starts a conversation, a chat ends, a transcript is ready, or a new ticket comes in. If you run tawk.to on your website, webhooks are how those conversations reach your CRM, ticketing, analytics or alerting without anyone copying them across by hand.
This guide covers how tawk.to webhooks are configured and signed, the four events and their payloads, the retry policy, and the practices that keep a tawk.to receiver reliable.
What are tawk.to webhooks?
A tawk.to webhook has three parts: a URL, one or more events to send to it, and a secret key used to prove that a request came from tawk.to. When one of the chosen events happens, tawk.to sends a POST with a JSON payload specific to that event.
Every request is signed. tawk.to computes an HMAC-SHA1 of the raw request body with the webhook's secret key and sends it in the X-Tawk-Signature header as a hex string. It also sends an X-Hook-Event-Id header that identifies the event and stays the same when a delivery is retried.
HMAC-SHA1 is tawk.to's only option. SHA-1 is no longer recommended for new hashing designs, but as an HMAC keyed with a secret it is still fit for authenticating a message, and it is what you have to implement to verify tawk.to.
tawk.to webhook features
| Feature | Details |
|---|---|
| Configuration | The Admin section of the tawk.to dashboard |
| Events | chat:start, chat:end, chat:transcript_created, ticket:create |
| Event identity | The event field in the body |
| Signature header | X-Tawk-Signature |
| Scheme | HMAC-SHA1, lowercase hex, no prefix |
| Signed content | The raw request body, nothing else |
| Secret | A secret key per webhook |
| Event ID header | X-Hook-Event-Id, unchanged across retries |
| Replay window | None. No timestamp is sent or signed |
| Response window | 30 seconds |
| Retries | Any non-2xx response or timeout, for up to 12 hours |
tawk.to publishes no source IP list for webhooks, and does not say how far apart its retries are.
Common events
tawk.to documents four events:
| Event | Fires when |
|---|---|
chat:start | The first message in a chat is sent by a visitor or agent. Whispers and system notifications such as triggers don't count |
chat:end | A chat ends |
chat:transcript_created | The full transcript of a chat session is generated after it has ended |
ticket:create | A new ticket is created |
Every payload has event, time (an ISO 8601 date-time) and a property object with the id and name of the tawk.to property the event belongs to. The rest depends on the event:
chat:startaddschatId,domain,referrer, avisitorobject (name,email,city,country) and the firstmessage, whosetypeismsg,fileorwebrtc-calland whosesender.typeisagent,visitororsystem.chat:endhas the same fields withoutmessage.chat:transcript_createdhasdomain,referrerand achatobject with the chatid, thevisitorand amessagesarray. Each message hastype,msg,time, asenderwith a one-lettert(aagent,vvisitor,ssystem), plus the sender's namenandidfor agents, and optionalattchsfor files.ticket:createhas arequester(name,email) and aticketwithid, a numerichumanId,subjectandmessage.
Here is the chat:start example from tawk.to's documentation, written out as JSON:
{
"event": "chat:start",
"chatId": "70fe3290-99ad-11e9-a30a-51567162179f",
"time": "2019-06-28T14:03:04.646Z",
"domain": "tawk.to",
"referrer": "https://www.google.com/",
"message": {
"text": "Sample message",
"type": "msg",
"sender": { "type": "visitor" }
},
"visitor": {
"name": "V1561719148780935",
"email": "hello@test.com",
"city": "jelgava",
"country": "LV"
},
"property": {
"id": "58ca8453b8a7e060cd3b1ecb",
"name": "Bobs Burgers"
}
}
tawk.to's own examples are JavaScript object literals, with unquoted keys and single-quoted strings, so they won't parse as JSON if you paste them into a test. The real deliveries are JSON. The visitor's email is documented as optional, so expect it to be missing for anonymous visitors.
Setting up tawk.to webhooks
- In the tawk.to dashboard, open the Admin section and go to the webhooks settings.
- Create a webhook with your HTTPS endpoint URL and choose the events to send.
- Copy the webhook's secret key and store it server-side, for example as
TAWK_WEBHOOK_SECRET. - Start a test chat on your site, or create a ticket, to trigger an event.
tawk.to's documentation only says that webhooks are managed in the Admin section, so menu labels may differ slightly from the steps above.
If you use Hookdeck, create a tawk.to source, set the secret key as its Webhook Signing Secret, and use the source URL as the webhook URL in tawk.to. tawk.to signs every request, so turn verification on.
Securing tawk.to webhooks
Compute an HMAC-SHA1 of the raw request body with the webhook's secret key, encode it as hex, and compare it with X-Tawk-Signature. tawk.to's samples compare with ===, which leaks timing information. Use a constant-time comparison instead.
const crypto = require('crypto');
const express = require('express');
function verifyTawkSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !secret) return false;
const expected = crypto.createHmac('sha1', secret).update(rawBody).digest('hex');
const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'utf8');
const computed = Buffer.from(expected, 'utf8');
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}
const app = express();
app.post('/webhooks/tawk-to', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.TAWK_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyTawkSignature(req.body, req.get('x-tawk-signature'), secret)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
const event_id = req.get('x-hook-event-id'); // stable across retries: dedupe on it
// Route on event.event. Persist, then process async.
res.sendStatus(200);
});
tawk.to's Node sample only keeps the raw body for application/json requests. express.raw({ type: '*/*' }) keeps it whatever the content type, so a change of header can't silently break verification.
The same check in Python:
import hashlib
import hmac
def verify_tawk_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header or not secret:
return False
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha1).hexdigest()
received = signature_header.strip().lower().encode("utf-8")
return hmac.compare_digest(received, expected.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_tawk_signature(raw_body, request.headers.get("x-tawk-signature"),
# os.environ["TAWK_WEBHOOK_SECRET"])
To check your verifier, use the secret tawk-test-secret and this exact body:
{"event":"chat:start","chatId":"70fe3290-99ad-11e9-a30a-51567162179f","time":"2019-06-28T14:03:04.646Z"}
The signature should be:
e3390d44732bf3413e039ab6d11134cab6902b67
Make tawk.to webhooks production-ready. Hookdeck Event Gateway verifies the
X-Tawk-Signatureheader, answers tawk.to well inside its 30-second window, and durably queues every chat and ticket event before your service sees it.
tawk.to webhook limitations and pain points
Twelve hours of retries, then nothing
The Problem: Events from a long outage are gone, and there is no documented way to ask for them again.
Why It Happens: tawk.to retries a failed delivery for up to 12 hours. A non-2xx response and a response slower than 30 seconds both count as failures. After that window, the documentation describes no further attempts and no redelivery feature.
Workarounds:
- Respond within 30 seconds by storing the event and processing it later.
- Monitor the endpoint so outages are fixed well inside 12 hours.
- Subscribe to
chat:transcript_createdas well as the chat events, so a missedchat:endcan be recovered from the transcript.
How Hookdeck Can Help: Hookdeck accepts each event as soon as tawk.to sends it and stores it, then retries delivery to your service on a schedule you control and lets you replay anything that failed, so the 12-hour limit applies to Hookdeck's uptime rather than yours.
No timestamp means no replay protection
The Problem: A captured request can be sent again later and still verify.
Why It Happens: The signature covers only the body. tawk.to sends no timestamp header and signs nothing else.
Workarounds:
- Deduplicate on
X-Hook-Event-Id, which tawk.to keeps the same across retries. - Keep the endpoint on HTTPS so requests can't be captured in transit.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on X-Hook-Event-Id before they reach your service.
Examples that aren't JSON
The Problem: Tests built from tawk.to's documentation fail to parse.
Why It Happens: The documented examples are JavaScript object literals with unquoted keys, single quotes and, in one case, a trailing comma. Some also differ slightly from the field tables, such as the type field on the example ticket requester.
Workarounds:
- Convert the examples to JSON before using them as fixtures.
- Capture real deliveries from a test property and use those instead.
- Treat fields that the tables don't list as optional.
How Hookdeck Can Help: Hookdeck records every real delivery with its headers and body, so you can copy genuine tawk.to payloads into your tests and replay them against your endpoint.
Best practices
Verify against the raw body
The HMAC covers the exact bytes tawk.to sent. In Express, use express.raw({ type: '*/*' }) and parse after verification. In Next.js, read await request.text(). In FastAPI, read await request.body().
Route on event
The event name is the top-level event field, and there is no event-type header. Switch on it, and log and acknowledge anything you don't recognise.
Deduplicate on X-Hook-Event-Id
The header is stable across retries, which makes it the natural idempotency key. Store it before you process the event. See our guide to webhook idempotency.
Expect missing visitor details
Visitor email is optional, and the generated visitor names (such as V1561719148780935) aren't real names. Match visitors to records in your CRM by email only when one is present.
Acknowledge fast and process asynchronously
tawk.to treats a response after 30 seconds as a failure and retries it. Verify, persist the event, return 200, and do the real work afterwards. See why to process webhooks asynchronously.
Conclusion
tawk.to sends four events, chat:start, chat:end, chat:transcript_created and ticket:create, each signed with a hex HMAC-SHA1 of the raw body in X-Tawk-Signature. Verify against the raw bytes with a constant-time comparison, route on the event field, deduplicate on X-Hook-Event-Id, and answer within 30 seconds so the 12-hour retry window never runs out on you.
Hookdeck Event Gateway sits in front of your receiver, checks the tawk.to signature, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle tawk.to webhooks reliably in minutes.