Agent skill

Chatim Webhooks Skill

Receive and verify Chatim webhooks. Use when setting up Chatim chat widget webhook handlers, debugging X-Chatim-Signature verification, or handling events like chat.started, chat.message.received, chat.form.submitted, chat.closed, chat.assigned, and chat.handoff.

Install this skill

npx skills add hookdeck/webhook-skills --skill chatim-webhooks


When to Use This Skill

  • How do I receive Chatim webhooks?
  • How do I verify the X-Chatim-Signature header?
  • How do I handle chat.started, chat.form.submitted, or chat.closed events?
  • Why is my Chatim webhook signature verification failing?
  • How do I send new chat leads from a Chatim widget to my CRM?

Essential Code (USE THIS)

Chatim signs every delivery with two headers:

X-Chatim-Signature: sha256=<hex>
X-Chatim-Timestamp: 1736937000
  • The signature is HMAC-SHA256 (lowercase hex) of <X-Chatim-Timestamp value>.<raw body>, keyed with the webhook secret (shown once when you create the webhook in the dashboard), used as-is (UTF-8, not decoded).
  • Accept the digest with or without the sha256= prefix, and compare hex case-insensitively.
  • The event name is in the body's event field (also sent in X-Chatim-Event). X-Chatim-Delivery-Id and the body's eventId are unique per delivery.
  • Answer a missing, malformed or wrong signature with 401. Chatim treats any 4xx as a permanent failure and does not retry, so don't return one for anything else.

Verification (Node.js)

const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyChatim(rawBody, signatureHeader, timestampHeader, secret) {
  if (!secret || !signatureHeader || !timestampHeader) return false;
  const hex = signatureHeader.startsWith('sha256=') ? signatureHeader.slice(7) : signatureHeader;
  if (!/^[0-9a-fA-F]{64}$/.test(hex)) return false; // validate before decoding
  const message = Buffer.concat([Buffer.from(timestampHeader, 'latin1'), Buffer.from('.'), rawBody]);
  const expected = crypto.createHmac('sha256', secret).update(message).digest();
  const received = Buffer.from(hex, 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

Verification (Python)

import hashlib, hmac, re

def verify_chatim(raw_body: bytes, signature_header, timestamp_header, secret: str) -> bool:
    if not secret or not signature_header or not timestamp_header:
        return False
    digest = signature_header.removeprefix("sha256=")
    if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):  # validate before decoding
        return False
    message = b"".join([timestamp_header.encode("latin-1"), b".", raw_body])
    expected = hmac.new(secret.encode(), message, hashlib.sha256).digest()
    return hmac.compare_digest(bytes.fromhex(digest), expected)

Chatim's own docs reject timestamps older than 5 minutes, and their Node sample signs JSON.stringify(req.body). The examples differ on both points:

  • They sign the raw body bytes (as Chatim's troubleshooting section says). Re-serialized JSON can differ from what was sent.
  • The timestamp check is off by default, because retries and replays (Chatim's and Hookdeck's) resend the original X-Chatim-Timestamp. Set CHATIM_WEBHOOK_TOLERANCE_SECONDS to a positive number of seconds (Chatim's examples use 300) to opt in. Use eventId for idempotency instead.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

Dispatch on the body's event field.

EventTriggered When
chat.startedA visitor opens the widget and begins a new session
chat.message.receivedA visitor sends a text message
chat.form.submittedA visitor submits a form in the chatbot flow
chat.closedA chat session is closed (by agent, visitor, system or timeout)
chat.assignedA support agent joins the conversation
chat.handoffThe chatbot transitions from bot mode to live chat

For payload shapes and the full list, see references/overview.md and Chatim's webhook docs.

Important Headers

HeaderDescription
X-Chatim-Signaturesha256=<hex> HMAC-SHA256 signature
X-Chatim-TimestampUnix timestamp (seconds) of when the request was sent
X-Chatim-EventThe event type
X-Chatim-Delivery-IdUnique delivery identifier

Environment Variables

CHATIM_WEBHOOK_SECRET=your_webhook_secret   # shown once when the webhook is created
CHATIM_WEBHOOK_TOLERANCE_SECONDS=0          # optional; a positive value opts in to the replay window

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 chatim --path /webhooks/chatim

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 10, 2026

View on GitHub →