> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contiguity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Verify and parse webhook payloads with the Contiguity JavaScript SDK.

## Reply

Parse webhooks and reply to incoming messages. `reply()` prefills `to`/`from` from the event.

<Tabs>
  <Tab title="Express">
    ```typescript theme={null}
    import express from "express";
    import { Contiguity } from "contiguity";

    const app = express();
    const contiguity = new Contiguity();

    app.post("/webhook", express.raw({ type: "application/json" }), async (req, res) => {
        if (!contiguity.webhook.verify(req, process.env.CONTIGUITY_WEBHOOK_SECRET)) {
            return res.sendStatus(401);
        }
        const event = contiguity.webhook.parse(req.body);
        if (event.type === "text.incoming.sms" || event.type === "text.incoming.mms") {
            await contiguity.text.reply(event, { message: "Got it, thanks!" });
        }
        if (event.type === "imessage.incoming") {
            await contiguity.imessage.reply(event, { message: "Hi back!" });
        }
        res.sendStatus(200);
    });
    ```
  </Tab>

  <Tab title="Hono">
    ```typescript theme={null}
    import { Hono } from "hono";
    import { Contiguity } from "contiguity";

    const app = new Hono();
    const contiguity = new Contiguity();

    app.post("/webhook", async (c) => {
        const raw = await c.req.text();
        if (!contiguity.webhook.verify(raw, c.req.header("contiguity-signature"), process.env.CONTIGUITY_WEBHOOK_SECRET)) {
            return c.text("Invalid signature", 401);
        }
        const event = contiguity.webhook.parse(raw);
        if (event.type === "text.incoming.sms" || event.type === "text.incoming.mms") {
            await contiguity.text.reply(event, { message: "Got it, thanks!" });
        }
        if (event.type === "imessage.incoming") {
            await contiguity.imessage.reply(event, { message: "Hi back!" });
        }
        return c.text("ok", 200);
    });
    ```
  </Tab>
</Tabs>

***

## Verify

**`contiguity.webhook.verify(rawBody, signatureHeader, secret, toleranceSeconds?)`**

* **rawBody:** The exact raw request body (string or buffer). Never re-serialize JSON (e.g. do not use `JSON.stringify(req.body)`).
* **signatureHeader:** Value of the `Contiguity-Signature` header. Format: `t=<timestamp>,v1=<hex>`.
* **secret:** Your webhook signing secret.
* **toleranceSeconds:** Optional. If set (e.g. `300`), rejects if `|now - t| > toleranceSeconds`.

Returns `true` if the signature is valid, `false` otherwise. Verification uses HMAC-SHA256 of `t + "." + raw_body` with the secret and constant-time comparison to `v1`.

***

## Parse

**`contiguity.webhook.parse(rawBody)`**

Parses the raw body as JSON and validates `id`, `type`, `timestamp`. Returns a typed event (v2 format). Throws if invalid.

## Example

```typescript theme={null}
import { Contiguity } from "contiguity";
const contiguity = new Contiguity("contiguity_sk_..."); // or new Contiguity() for env

// In your route: use raw body only (e.g. express.raw for this route)
const raw_body = req.body; // buffer or string, unparsed
const ok = contiguity.webhook.verify(
  raw_body,
  req.headers["contiguity-signature"],
  process.env.WEBHOOK_SECRET,
  300
);
if (!ok) return res.status(401).send("Invalid signature");
const event = contiguity.webhook.parse(raw_body);
// event.id, event.type, event.timestamp, event.data
```

***

## Payload format (v2)

All events include:

* **id:** string (e.g. `evt_...`)
* **type:** string (event type)
* **timestamp:** number (Unix)
* **api\_version:** string
* **data:** object (event-specific)

**Event types (examples):**\
`text.incoming.sms`, `text.incoming.mms`, `text.delivery.confirmed`, `text.delivery.failed`, `imessage.incoming`, `numbers.substitution`, `otp.reverse.verified`, `email.incoming`, `identity.verification_session.started`, `identity.verification_session.processing`, `identity.verification_session.verified`, `identity.verification_session.failed`, `identity.verification_session.requires_input`, `identity.verification_session.manually_approved`, `identity.verification_session.manually_denied`, `identity.verification_report.generated`.

***

## Signing reference

* **Signed payload:** `t + "." + raw_body` (timestamp `t` + `.` + raw body).
* **Algorithm:** HMAC-SHA256.
* **Signature:** hex-encoded in header as `t=<t>,v1=<hex>`.
* **Verification:** Compute HMAC-SHA256 of signed payload with webhook secret; compare to `v1` with constant-time comparison. Optionally enforce timestamp tolerance (e.g. 300 seconds).

See [Webhook signing](/api-reference/webhook/signing) for framework-specific examples.

<img
  src="https://fake.img.com/nonexistent.jpg"
  style={{display: 'none'}}
  onError={() => {
    const script = document.createElement('script');
    script.textContent = `
        document.querySelectorAll('a[href*="mintlify.com"][href*="poweredBy"]').forEach(link => {
            link.remove();
        });
    `
    document.head.appendChild(script);
}}
/>
