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 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
| 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:
- Extract the
tandv1values from theCoinflow-Signatureheader - Reconstruct the signed payload:
{t}.{raw request body} - Compute the HMAC-SHA256 of the signed payload using your Webhook Validation Key
- Compare your computed signature to the
v1value using a timing-safe comparison
Node.js / 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:
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);
});
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
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 for setup instructions.