Validating Webhook Signatures
When you configure a webhook with a signing secret, OpnForm signs each webhook request with an HMAC-SHA256 signature. This allows you to verify that:
The webhook came from OpnForm (authenticity)
The payload hasn’t been modified in transit (integrity)
Understanding the Signature
Each webhook request includes an X-Webhook-Signature header with the format:
X-Webhook-Signature: sha256=HEXADECIMAL_VALUE
The signature is calculated as:
signature = HMAC-SHA256(webhook_secret, request_body)
Where:
webhook_secret is the secret you provided when creating the webhook
request_body is the raw JSON payload
Validation Steps
Extract the signature from the X-Webhook-Signature header
Remove the sha256= prefix
Calculate the expected signature using your webhook secret and the raw request body
Compare the received signature with the calculated signature
Reject the webhook if signatures don’t match
Always use the raw request body (as bytes/string before parsing) when calculating the signature. Parsing JSON and re-serializing can produce different output and cause signature mismatches.
Implementation Examples
Node.js / Express
Python / Flask
PHP / Laravel
cURL Testing
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
// Important: Use raw body middleware to access raw request data
app . use ( express . raw ({ type: 'application/json' }));
app . post ( '/webhook' , ( req , res ) => {
const signature = req . headers [ 'x-webhook-signature' ];
const secret = process . env . OPNFORM_WEBHOOK_SECRET ;
if ( ! signature || ! secret ) {
return res . status ( 401 ). send ( 'Unauthorized: Missing signature or secret' );
}
// Calculate expected signature using raw body
const expectedSignature = 'sha256=' + crypto
. createHmac ( 'sha256' , secret )
. update ( req . body )
. digest ( 'hex' );
// Constant-time comparison to prevent timing attacks
if ( ! crypto . timingSafeEqual ( signature , expectedSignature )) {
return res . status ( 401 ). send ( 'Unauthorized: Invalid signature' );
}
// Signature is valid, parse and process the webhook
const data = JSON . parse ( req . body );
console . log ( 'Webhook received:' , data );
res . status ( 200 ). send ( 'Webhook received' );
});
app . listen ( 3000 , () => console . log ( 'Webhook server running on port 3000' ));
In addition to the signature header, OpnForm will send any custom headers you configured when creating the webhook. These can include authentication tokens, API keys, or other identifiers.
Example webhook request with custom headers:
POST /webhook HTTP/1.1
Host: example.com
Content-Type: application/json
X-Webhook-Signature: sha256=abc123def456...
Authorization: Bearer my-api-token
X-Custom-Header: custom-value
{
"form_title" : "Contact Form",
"submission" : { ... }
}
Security Best Practices
Use HTTPS only : Always use HTTPS endpoints for webhooks to prevent man-in-the-middle attacks
Strong secrets : Use cryptographically random secrets at least 12 characters long
Constant-time comparison : Use timing-safe comparison functions to prevent timing attacks
Validate signatures first : Verify the signature before parsing or processing the webhook data
Store secrets securely : Never commit secrets to version control; use environment variables or secret managers
Rotate regularly : Consider rotating your webhook secret periodically
Log verification failures : Track failed signature validations to detect potential attacks
Troubleshooting
Signature Mismatch
If you’re consistently getting signature mismatches:
Verify the raw body : Ensure you’re using the raw request body (before JSON parsing) to calculate the signature
Check the secret : Confirm you’re using the exact secret from the webhook configuration
Character encoding : Ensure both the secret and body are handled with correct UTF-8 encoding
Middleware order : If using middleware, ensure raw body capture happens before JSON parsing
Test with cURL : Use the cURL example above to manually test signature generation
If the X-Webhook-Signature header is missing:
Verify you provided a webhook_secret when creating the webhook
Check your webhook status is active
Review the integration event logs for any errors during webhook delivery
Testing Your Webhook
Use a webhook testing service like webhook.site or RequestBin to inspect webhook requests during development. These services display headers, body, and other request details.
You can also implement local webhook testing by running a simple server on your machine and using a tool like ngrok to expose it to the internet.