Launch offer Get 50% off every monthly plan for a limited time. View plans →

Verifying webhook signatures

Every delivery includes an X-ReachCell-Signature header formatted as sha256=<hex>. The signature is an HMAC-SHA256 over the raw request body.

The HMAC key is the SHA-256 hash of your webhook secret (shown once at webhook creation). To verify:

  1. Read the raw request body before parsing JSON.
  2. Compute key = sha256(your_secret) as raw binary bytes.
  3. Compute HMAC-SHA256(raw_body, key) and hex-encode it.
  4. Prefix with sha256= and compare to the header using a timing-safe comparison.

If the signature does not match, reject the delivery with HTTP 400.

import hashlib, hmac
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"

@app.post("/webhooks/reachcell")
def handle():
    sig_header = request.headers.get("X-ReachCell-Signature", "")
    raw_body   = request.get_data()

    key      = hashlib.sha256(WEBHOOK_SECRET.encode()).digest()
    expected = "sha256=" + hmac.new(key, raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected, sig_header):
        abort(400, "Bad signature")

    event = request.json
    print(event["event_type"], event["data"])
    return "", 200
<?php
$secret    = 'your_webhook_secret';
$rawBody   = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_REACHCELL_SIGNATURE'] ?? '';

$key      = hash('sha256', $secret, true);          // raw binary
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $key);

if (!hash_equals($expected, $sigHeader)) {
    http_response_code(400);
    exit('Bad signature');
}

$event = json_decode($rawBody, true);
// $event['event_type'], $event['data']
import crypto from 'crypto';
// Requires express.raw() middleware so req.rawBody is available

app.post('/webhooks/reachcell', (req, res) => {
  const sig    = req.headers['x-reachcell-signature'] ?? '';
  const raw    = req.rawBody;

  const key      = crypto.createHash('sha256')
                         .update(process.env.WEBHOOK_SECRET)
                         .digest();          // raw binary
  const expected = 'sha256=' +
    crypto.createHmac('sha256', key).update(raw).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.sendStatus(400);
  }

  const event = JSON.parse(raw);
  console.log(event.event_type, event.data);
  res.sendStatus(200);
});
© 2026 ReachCell · Terms · Privacy · AUP · API v1 · support@reachcell.com