Gareth Wilson Gareth Wilson

Guide to tawk.to Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationThe Admin section of the tawk.to dashboard
Eventschat:start, chat:end, chat:transcript_created, ticket:create
Event identityThe event field in the body
Signature headerX-Tawk-Signature
SchemeHMAC-SHA1, lowercase hex, no prefix
Signed contentThe raw request body, nothing else
SecretA secret key per webhook
Event ID headerX-Hook-Event-Id, unchanged across retries
Replay windowNone. No timestamp is sent or signed
Response window30 seconds
RetriesAny 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:

EventFires when
chat:startThe first message in a chat is sent by a visitor or agent. Whispers and system notifications such as triggers don't count
chat:endA chat ends
chat:transcript_createdThe full transcript of a chat session is generated after it has ended
ticket:createA 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:start adds chatId, domain, referrer, a visitor object (name, email, city, country) and the first message, whose type is msg, file or webrtc-call and whose sender.type is agent, visitor or system.
  • chat:end has the same fields without message.
  • chat:transcript_created has domain, referrer and a chat object with the chat id, the visitor and a messages array. Each message has type, msg, time, a sender with a one-letter t (a agent, v visitor, s system), plus the sender's name n and id for agents, and optional attchs for files.
  • ticket:create has a requester (name, email) and a ticket with id, a numeric humanId, subject and message.

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

  1. In the tawk.to dashboard, open the Admin section and go to the webhooks settings.
  2. Create a webhook with your HTTPS endpoint URL and choose the events to send.
  3. Copy the webhook's secret key and store it server-side, for example as TAWK_WEBHOOK_SECRET.
  4. 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-Signature header, 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_created as well as the chat events, so a missed chat:end can 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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.