> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.coinflow.cash/guides/developer-resources/webhooks/verifying-webhook-signatures/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.