SMSLocal
API & Integrations

How webhooks are signed and verified

Every SMSLocal webhook includes an HMAC-SHA256 signature and timestamp. Reject requests older than 5 minutes and verify the signature before trusting any incoming payload.

4 min readUpdated 28 Mar 2026

Webhook endpoints are public by definition. To confirm that an incoming request actually came from SMSLocal and wasn't tampered with, verify the signature in every webhook header.

Verification steps

  1. 01Read the X-SMSLocal-Signature header — it contains a timestamp and an HMAC.
  2. 02Reject requests where the timestamp is older than 5 minutes (prevents replay).
  3. 03Compute HMAC-SHA256 over 'timestamp.raw_body' using your webhook secret.
  4. 04Compare the computed HMAC with the one in the header using constant-time equality.
  5. 05Only act on the payload if the HMACs match.
import crypto from "node:crypto"

function verify(req, secret) {
  const [tsPart, sigPart] = req.headers["x-smslocal-signature"].split(",")
  const ts = tsPart.split("=")[1]
  const sig = sigPart.split("=")[1]
  if (Date.now() / 1000 - Number(ts) > 300) return false
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`)
    .digest("hex")
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
}

Did this article help?

If something isn't clear or the steps don't match what you're seeing, tell us and we'll fix the doc the same day.