Signing webhook requests
To ensure that incoming webhook requests are authentic and truly sent by Returnless, each webhook request is signed using a signing secret. This allows you to verify the integrity of the payload before processing it in your application.
How signing works
When you create a webhook in Returnless, you receive a signing secret. This secret is unique per webhook and should be stored securely on your side.
(See Creating webhooks for instructions on generating a webhook and retrieving the secret.)
Whenever Returnless sends a webhook request to your endpoint:
- We generate a HMAC SHA-256 hash of the full request body.
- The hash is created using your webhook’s signing secret.
- The resulting hash is included in the
X-Signatureheader of the request.
On your end, you can compute the same HMAC hash using your signing secret and compare it to the value in the X-Signature header.
If the two hashes match, the request is valid and has not been tampered with.
Verifying the signature in your application
The method for generating an HMAC SHA-256 hash depends on the programming language you use. The general verification flow is:
- Read the raw request payload.
- Compute a HMAC SHA-256 hash using your signing secret.
- Compare that hash with the value from the
X-Signatureheader. - If they match, continue processing the webhook. If not, reject the request.
Below are example implementations.
Example: PHP
$secret = '[SIGNING_SECRET]';
$payload = file_get_contents('php://input');
$hash = hash_hmac('sha256', $payload, $secret);
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? null;
if ($hash !== $signature) {
throw new Exception('Invalid signature.');
}
Example: python
import hashlib
import hmac
signature = request.META['HTTP_X_SIGNATURE']
secret = '[SIGNING_SECRET]'
digest = hmac.new(secret.encode(), request.body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(digest, signature):
raise Exception('Invalid signature.')
Example: javascript
const crypto = require('crypto');
const secret = '[SIGNING_SECRET]';
const hmac = crypto.createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(request.rawBody).digest('hex'), 'utf8');
const signature = Buffer.from(request.get('X-Signature') || '', 'utf8');
if (!crypto.timingSafeEqual(digest, signature)) {
throw new Error('Invalid signature.');
}