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:

  1. We generate a HMAC SHA-256 hash of the full request body.
  2. The hash is created using your webhook’s signing secret.
  3. The resulting hash is included in the X-Signature  header 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:

  1. Read the raw request payload.
  2. Compute a HMAC SHA-256 hash using your signing secret.
  3. Compare that hash with the value from the X-Signature  header.
  4. 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.');
}
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us