For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
RegisterLoginSandbox Login
GuidesRecipesAPI Reference
GuidesRecipesAPI Reference
  • Getting Started
    • Getting Started with Checkout
    • Account Setup
    • Card Checkout with Credits
    • Card Checkout
    • Direct USDC Settlement
    • Fiat/Crypto Pay-ins
    • Secure Marketplace Checkout
    • EVM Checkout
    • How to Enable Checkout with Credit Cards
    • Quick Start Marketplace Implementation
    • Payouts
    • Common FAQs
  • Checkout
    • Settlement Locations
  • Payouts
    • Payout Overview
    • What is a Payout
  • Subscriptions
    • Subscriptions Overview
  • Marketplaces
    • Marketplace Overview
    • How Marketplaces Work
    • How to Withdraw USDC
    • Countries Eligible for USDC Withdraw
    • Marketplaces Webhooks
    • Marketplaces Implementation
  • Developer Resources
    • Custom Branding
    • Checkout Implementation
    • Webhooks
      • Configuring Webhooks
      • Verifying Webhook Signatures
      • Withdraw Webhooks
      • Checkout Webhooks
  • Merchant Dashboard
    • Login & Account Access
    • Users and Roles
    • Rate Limits
    • Developer Contact
LogoLogo
RegisterLoginSandbox Login
On this page
  • How It Works
  • Verifying the Signature
  • Node.js / TypeScript
  • Python
  • Where to Find Your Webhook Validation Key
Developer ResourcesWebhooks

Verifying Webhook Signatures

Was this page helpful?
Previous

Withdraw Webhooks

Developers can use this documentation to listen to webhooks associatied with KYC/B and Withdrawals.

Next
Built with

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 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:

Coinflow-Signature: t=1717012345,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
ComponentDescription
tUnix timestamp (seconds) of when the signature was generated
v1HMAC-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

1import crypto from 'node:crypto';
2
3function verifyWebhookSignature({
4 signatureHeader,
5 payload,
6 secret,
7}: {
8 signatureHeader: string;
9 payload: string;
10 secret: string;
11}): boolean {
12 const parts = signatureHeader.split(',');
13 let timestamp: string | undefined;
14 let signature: string | undefined;
15
16 for (const part of parts) {
17 const [key, value] = part.split('=', 2);
18 if (key === 't') timestamp = value;
19 else if (key === 'v1') signature = value;
20 }
21
22 if (!timestamp || !signature) {
23 throw new Error('Invalid Coinflow-Signature header');
24 }
25
26 const signedPayload = `${timestamp}.${payload}`;
27 const expected = crypto
28 .createHmac('sha256', secret)
29 .update(signedPayload)
30 .digest('hex');
31
32 return crypto.timingSafeEqual(
33 Buffer.from(signature),
34 Buffer.from(expected)
35 );
36}

Usage in an Express route:

1app.post('/coinflow-webhook', (req, res) => {
2 const signatureHeader = req.headers['coinflow-signature'] as string;
3 const rawBody = req.body; // Must be the raw string body, not parsed JSON
4
5 const isValid = verifyWebhookSignature({
6 signatureHeader,
7 payload: rawBody,
8 secret: process.env.COINFLOW_VALIDATION_KEY!,
9 });
10
11 if (!isValid) {
12 return res.status(401).send('Invalid signature');
13 }
14
15 // Process the webhook
16 const event = JSON.parse(rawBody);
17 handleEvent(event);
18
19 res.sendStatus(200);
20});

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

1import hmac
2import hashlib
3
4def verify_webhook_signature(signature_header: str, payload: str, secret: str) -> bool:
5 parts = dict(p.split("=", 1) for p in signature_header.split(","))
6 timestamp = parts.get("t")
7 signature = parts.get("v1")
8
9 if not timestamp or not signature:
10 raise ValueError("Invalid Coinflow-Signature header")
11
12 signed_payload = f"{timestamp}.{payload}"
13 expected = hmac.new(
14 secret.encode(), signed_payload.encode(), hashlib.sha256
15 ).hexdigest()
16
17 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 for setup instructions.