Skip to content

Anyone who discovers your webhook URL can POST fake events to it. A signing secret lets you verify that events really come from 23 Telecom.

Set a signing secret in the portal (Settings → Webhooks) or via PUT /user/webhooks. Every webhook request then includes:

HeaderValue
X-Webhook-TimestampUnix timestamp (seconds)
X-Webhook-Signaturesha256={hex}

Verification:

expected = HMAC-SHA256(your_secret, "{timestamp}.{raw_body}")
compare: "sha256=" + expected == X-Webhook-Signature

Three rules:

  • Compute over the raw request body — before any JSON parsing or re-serialization.
  • Use constant-time comparison (hmac.compare_digest, crypto.timingSafeEqual, hash_equals) — never == on signatures.
  • Reject stale timestamps (e.g. older than 5 minutes) to limit replay windows.

Verification code in Python, Node.js and PHP is included in the delivery webhook examples.

The secret is write-only: GET /user/webhooks returns only signing_secret_set: true|false, never the value.

OperationPUT body
Preserve existing secretOmit signing_secret, or send signing_secret: null
Set / rotate secret{ "signing_secret": "your-new-secret" }
Clear secret{ "clear_signing_secret": true }
Invalid (400 INVALID_SECRET)signing_secret: "" or longer than 64 chars
  • HTTPS only — webhook URLs must be TLS; plain HTTP exposes payloads and signatures in transit.
  • Don’t reflect errors verbosely — a 401 with no body is enough for a failed signature.
  • Idempotency by message_id — replayed or retried events must not double-process.
  • Secret hygiene — generate 32+ random characters, store alongside your other secrets, rotate on staff changes.