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

# Verify signatures

> Check that a request comes from TIKT and was not changed on the way, with the official Standard Webhooks library.

TIKT signs every request the way the [Standard Webhooks](https://www.standardwebhooks.com/) specification describes. Checking the signature proves two things: the request comes from TIKT, and its body is exactly what TIKT sent. **Our IP addresses are not fixed: the signature is the way to know a request is ours.**

## What is signed

Three headers come with each request:

| Header | Value |
| - | - |
| `webhook-id` | The message's identifier. |
| `webhook-timestamp` | When this attempt was sent, in Unix seconds. |
| `webhook-signature` | `v1,` followed by the signature in base64. |

The signature is an HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, where `body` is the raw request body, byte for byte. The key is the destination's signing secret without its `whsec_` prefix, base64-decoded.

Reject a request whose timestamp is more than **5 minutes** away from your clock: it protects you from a replayed request. The libraries below do it for you.

<Warning>
  Verify the **raw** body. A body parsed into JSON, then serialized again, no longer has the same bytes, and the signature won't match.
</Warning>

## Node

```bash theme={null}
npm install standardwebhooks
```

<CodeGroup>
  ```javascript Express theme={null}
  import express from "express";
  import { Webhook } from "standardwebhooks";

  const app = express();
  const webhook = new Webhook(process.env.TIKT_SIGNING_SECRET); // whsec_…

  // express.raw keeps the body as bytes: the signature is computed on them.
  app.post("/tikt", express.raw({ type: "application/json" }), (req, res) => {
    let event;
    try {
      event = webhook.verify(req.body, req.headers);
    } catch {
      return res.status(400).send("invalid signature");
    }
    if (event.type === "lead.created") {
      // event.data.lead, event.data.signal
    }
    res.sendStatus(200);
  });

  app.listen(3000);
  ```

  ```javascript Next.js route theme={null}
  import { Webhook } from "standardwebhooks";

  const webhook = new Webhook(process.env.TIKT_SIGNING_SECRET); // whsec_…

  export async function POST(request) {
    const body = await request.text();
    let event;
    try {
      event = webhook.verify(body, Object.fromEntries(request.headers));
    } catch {
      return new Response("invalid signature", { status: 400 });
    }
    // event.type, event.data
    return new Response(null, { status: 200 });
  }
  ```
</CodeGroup>

## Python

```bash theme={null}
pip install standardwebhooks
```

```python Flask theme={null}
import os
from flask import Flask, request
from standardwebhooks import Webhook, WebhookVerificationError

app = Flask(__name__)
webhook = Webhook(os.environ["TIKT_SIGNING_SECRET"])  # whsec_…

@app.post("/tikt")
def tikt():
    try:
        event = webhook.verify(request.get_data(), dict(request.headers))
    except WebhookVerificationError:
        return "invalid signature", 400
    if event["type"] == "lead.created":
        lead = event["data"]["lead"]
    return "", 200
```

## Other languages

Standard Webhooks publishes libraries for other languages, all compatible with TIKT's signature: see [standardwebhooks.com](https://www.standardwebhooks.com/). You can also compute the HMAC yourself from the description above.

## Rotating the secret

In the destination, **Rotate** creates a new secret and shows it once. From that moment, each request is signed with both secrets for **24 hours**: `webhook-signature` carries two signatures separated by a space, the new one first. Update your endpoint during those 24 hours; after them, only the new secret signs. The libraries accept a request when one of the signatures matches.

## Without code

Tools like Zapier or Make can't always compute a signature. Two simpler options, set in the destination under **More options**:

* **A header of your own**, for example `Authorization: Bearer <a long random value>`, that your tool checks. TIKT stores its value encrypted and never shows it again.
* **Keep the address secret.** The address your tool gives you is hard to guess; don't share it.

A header proves less than a signature (it doesn't protect the body), but it keeps strangers out.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.