# 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)

```javascript
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)

```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/](https://github.com/hookdeck/webhook-skills/tree/main/skills/chatim-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/chatim-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/chatim-webhooks/examples/fastapi/).

## Common Event Types

Dispatch on the body's `event` field.

| Event | Triggered When |
| --- | --- |
| `chat.started` | A visitor opens the widget and begins a new session |
| `chat.message.received` | A visitor sends a text message |
| `chat.form.submitted` | A visitor submits a form in the chatbot flow |
| `chat.closed` | A chat session is closed (by `agent`, `visitor`, `system` or `timeout`) |
| `chat.assigned` | A support agent joins the conversation |
| `chat.handoff` | The chatbot transitions from bot mode to live chat |

> **For payload shapes and the full list**, see [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/chatim-webhooks/references/overview.md) and [Chatim's webhook docs](https://chatim.app/en/documentation/webhooks/).

## Important Headers

| Header | Description |
| --- | --- |
| `X-Chatim-Signature` | `sha256=<hex>` HMAC-SHA256 signature |
| `X-Chatim-Timestamp` | Unix timestamp (seconds) of when the request was sent |
| `X-Chatim-Event` | The event type |
| `X-Chatim-Delivery-Id` | Unique delivery identifier |

## Environment Variables

```bash
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

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/chatim-webhooks/references/overview.md) - Chatim webhook concepts, events, payloads, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/chatim-webhooks/references/setup.md) - Creating a webhook and getting the secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/chatim-webhooks/references/verification.md) - Signature verification details and gotchas