> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coinflow.cash/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coinflow.cash/_mcp/server.

# Verifying Webhook Signatures

Every webhook Coinflow sends includes a `Coinflow-Signature` header containing an HMAC-SHA256 signature of the request body. You can use this signature to verify that a webhook was sent by Coinflow and that its payload has not been tampered with.

This is an alternative to the [Authorization header approach](/guides/developer-resources/webhooks/configuring-webhooks) which should be used in the case of an overriden authorization header.

## How It Works

When Coinflow sends a webhook, it signs the JSON body using your **Webhook Validation Key** and attaches the signature in the `Coinflow-Signature` header. The header has this format:

```Text
Coinflow-Signature: t=1717012345,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

| Component | Description                                                  |
| --------- | ------------------------------------------------------------ |
| `t`       | Unix timestamp (seconds) of when the signature was generated |
| `v1`      | HMAC-SHA256 hex digest of the signed payload                 |

The **signed payload** is the timestamp and the raw JSON body joined by a dot: `{timestamp}.{body}`.

## Verifying the Signature

To verify a webhook signature:

1. Extract the `t` and `v1` values from the `Coinflow-Signature` header
2. Reconstruct the signed payload: `{t}.{raw request body}`
3. Compute the HMAC-SHA256 of the signed payload using your Webhook Validation Key
4. Compare your computed signature to the `v1` value using a timing-safe comparison

### Node.js / TypeScript

```typescript
import crypto from 'node:crypto';

function verifyWebhookSignature({
  signatureHeader,
  payload,
  secret,
}: {
  signatureHeader: string;
  payload: string;
  secret: string;
}): boolean {
  const parts = signatureHeader.split(',');
  let timestamp: string | undefined;
  let signature: string | undefined;

  for (const part of parts) {
    const [key, value] = part.split('=', 2);
    if (key === 't') timestamp = value;
    else if (key === 'v1') signature = value;
  }

  if (!timestamp || !signature) {
    throw new Error('Invalid Coinflow-Signature header');
  }

  const signedPayload = `${timestamp}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
```

Usage in an Express route:

```typescript
app.post('/coinflow-webhook', (req, res) => {
  const signatureHeader = req.headers['coinflow-signature'] as string;
  const rawBody = req.body; // Must be the raw string body, not parsed JSON

  const isValid = verifyWebhookSignature({
    signatureHeader,
    payload: rawBody,
    secret: process.env.COINFLOW_VALIDATION_KEY!,
  });

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process the webhook
  const event = JSON.parse(rawBody);
  handleEvent(event);

  res.sendStatus(200);
});
```

> **Note**
>
> You must verify the signature against the **raw request body string**, not a parsed-and-re-serialized JSON object. Re-serializing can change whitespace or key order, which will cause verification to fail.

### Python

```python
import hmac
import hashlib

def verify_webhook_signature(signature_header: str, payload: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp = parts.get("t")
    signature = parts.get("v1")

    if not timestamp or not signature:
        raise ValueError("Invalid Coinflow-Signature header")

    signed_payload = f"{timestamp}.{payload}"
    expected = hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)
```

## Where to Find Your Webhook Validation Key

Your Webhook Validation Key is available in the Coinflow Admin Dashboard under **Developers → Webhooks**. See [Configuring Webhooks](/guides/developer-resources/webhooks/configuring-webhooks) for setup instructions.