Webhook Signature

All webhooks sent from Coinify are signed with a shared secret that is known only by you and Coinify. This ensures the integrity of the data contained in the webhook, and also proves that Coinify is the sender of the webhook (provided the shared secret is not known by anyone else).

Specifically, the signature uses HMAC-SHA256, using the shared secret as the key and the full HTTP request body (UTF-8 encoded) as the message. The resulting signature is provided in lowercase hexadecimal format in the X-Coinify-Webhook-Signature HTTP header.

Signature example

Use the following example to test that your signature validation function is working correctly

Shared secret: my-shared-secret,

Payload: {"examplePayload":true}

Expected signature: bcdbb89e3031905f3cc1a20d16b5f969a17a7d8fa0c26e4a807c2193402d66f4


JavaScript code to validate a webhook event signature

const crypto = require('crypto');
const sharedSecret = 'shared-secret';

// Use the RAW body string directly from the request
// (Avoid JSON.parse -> JSON.stringify which can change formatting)
const rawBody = req.rawBody; // Or however your framework preserves the unparsed string
const signature = req.headers['x-coinify-webhook-signature'];

const hash = crypto.createHmac('sha256', sharedSecret)
  .update(rawBody, 'utf8')
  .digest('hex');

return hash === signature;

Python code to validate a webhook event signature

import hashlib
import hmac

# 1. The secret must be bytes
shared_secret = 'the_shared_secret'.encode('utf-8')

# 2. Get the raw HTTP POST body as bytes
# If your framework gives you a string, use .encode('utf-8')
body = get_body() 
if isinstance(body, str):
    body = body.encode('utf-8')

# 3. Get the signature from headers
signature = get_header("X-Coinify-Webhook-Signature")

# 4. Generate the expected signature
# hmac.new expects (key, msg, digestmod)
expected_signature = hmac.new(
    shared_secret, 
    msg=body, 
    digestmod=hashlib.sha256
).hexdigest()

# 5. Use a constant-time comparison to prevent timing attacks
return hmac.compare_digest(signature, expected_signature)