Webhook Signature Verification
Breeze signs every webhook event sent to your server using an HMAC-SHA256 signature. You should always verify this signature before trusting the webhook payload.
🚩 How Signature Verification Works
Step 1: Sort Data
Sort all keys in the webhook data payload alphabetically in ascending order and stringify them as JSON.
For example, given this webhook data:
{
"amount": 500,
"clientReferenceId": "testClientReferenceId",
"currency": "USD",
"pageId": "page_8d78100e18bc825f",
"status": "PAID"
}
Sorted and stringified, the JSON string is:
{"amount":500,"clientReferenceId":"testClientReferenceId","currency":"USD","pageId":"page_8d78100e18bc825f","status":"PAID"}
Step 2: Generate Signature
Using your webhook secret, create an HMAC-SHA256 hash of this JSON string, then encode it to Base64.
For example, if your webhook secret is testwebhooksecret
, your resulting signature is: afZiTJOjqNBTWTLVuP4/bhY1dwUNxo+P8z1Rb1fUPSU=
✅ Examples
💡 Don’t see your preferred language here? Just let us know at [email protected]—we’ll gladly help you set it up!
import stringify from "json-stable-stringify";
import { createHmac, timingSafeEqual } from "crypto";
// Express webhook handler
app.post("/webhook", (request, response) => {
const { type, signature, data } = request.body;
// Your webhook secret from dashboard.breeze.cash
const webhookSecret = "whook_sk_xxxxxx";
// Generate expected signature
const expectedSignature = createHmac("sha256", webhookSecret)
.update(stringify(data))
.digest("base64");
// Securely compare signatures using timingSafeEqual
const signatureIsValid = timingSafeEqual(
Buffer.from(signature, "utf8"),
Buffer.from(expectedSignature, "utf8")
);
if (!signatureIsValid) {
console.error("Invalid webhook signature!");
return response.status(400).send();
}
// Handle the webhook payload
console.log(`type: ${type}`);
console.log(`data:`, data);
// Acknowledge successful processing to Breeze
response.status(200).send();
});
import json
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
# Your webhook secret from dashboard.breeze.cash
WEBHOOK_SECRET = 'whook_sk_xxxxxx'
def verify_signature(data, provided_signature, secret):
sorted_data = json.dumps(data, separators=(',', ':'), sort_keys=True)
expected_hmac = hmac.new(
secret.encode('utf-8'),
sorted_data.encode('utf-8'),
hashlib.sha256
).digest()
expected_signature = base64.b64encode(expected_hmac).decode('utf-8')
# Securely compare signatures to prevent timing attacks
return hmac.compare_digest(provided_signature, expected_signature)
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_json()
data = payload.get('data', {})
signature = payload.get('signature', '')
event_type = payload.get('type', '')
if not verify_signature(data, signature, WEBHOOK_SECRET):
print('Invalid webhook signature!')
abort(400)
# Signature is valid—process webhook event
print(f"Event Type: {event_type}")
print(f"Data: {data}")
# Acknowledge successful processing to Breeze
return '', 200
if __name__ == '__main__':
app.run(port=5000)
<?php
// Your webhook secret from dashboard.breeze.cash
$webhookSecret = 'whook_sk_xxxxxx';
// Retrieve the raw POST body
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
// Extract the signature and data from the payload
$signature = $data['signature'] ?? '';
$eventType = $data['type'] ?? '';
$eventData = $data['data'] ?? [];
// Sort keys in $eventData and encode as JSON
ksort($eventData);
$sortedJson = json_encode($eventData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// Compute HMAC SHA256 and Base64 encode it
$expectedSignature = base64_encode(hash_hmac('sha256', $sortedJson, $webhookSecret, true));
// Securely compare signatures
if (!hash_equals($expectedSignature, $signature)) {
error_log('Invalid webhook signature!');
http_response_code(400);
exit;
}
// Handle webhook event
error_log("Webhook received: Type: $eventType");
error_log("Data: " . print_r($eventData, true));
// Respond with HTTP 200 to acknowledge webhook
http_response_code(200);
📌 Important Notes
- Always compare signatures securely.
- Your webhook handler should respond with HTTP status 200 after successfully processing the webhook. Otherwise, Breeze will retry the webhook.
- Never expose your webhook secret publicly.
Updated 5 months ago