Skip to content
Webhooks

SMS webhooks your endpoint can actually trust.

Subscribe to inbound messages, delivery receipts and call events. VanceTel pushes each one to your endpoint as soon as it happens — and signs every request with HMAC-SHA256 so you can verify it came from us.

HMAC-SHA256 signedAutomatic retriesTyped events
message.received payload
POST /your-endpoint  HTTP/1.1
X-Netexem-Signature: t=1717000000,v1=8a1f...c4
Content-Type: application/json

{
  "id": "evt_3pQ2x9KdLm",
  "type": "message.received",
  "createdAt": "2026-05-31T14:02:11Z",
  "data": {
    "id": "msg_7nF1aZ",
    "from": "+15557654321",
    "to": "+15551234567",
    "body": "Yes — 2pm works, thanks!",
    "direction": "inbound"
  }
}

A signed inbound-message event, delivered the moment a text arrives.

HMAC webhook verification

Verify before you trust the payload.

Each request carries a timestamp and an HMAC-SHA256 digest of the raw body. Recompute it with your endpoint secret and compare in constant time. If it does not match, drop the request — no exceptions.

  • Sign the raw request bytes, not the re-serialized JSON.
  • Use a constant-time compare to resist timing attacks.
  • Reject stale timestamps to blunt replayed requests.
verify.ts — Node, HMAC-SHA256
import crypto from 'node:crypto'

// Verify an HMAC-SHA256 webhook signature.
// Use the RAW request body — never the re-serialized JSON.
function verify(rawBody, header, secret) {
  const [, ts] = header.match(/t=(\d+)/) ?? []
  const [, sig] = header.match(/v1=([a-f0-9]+)/) ?? []
  if (!ts || !sig) return false

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex')

  // Constant-time compare to resist timing attacks.
  const a = Buffer.from(sig, 'hex')
  const b = Buffer.from(expected, 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
Event catalog

Subscribe to the events you care about.

Pick events when you register an endpoint. VanceTel delivers only those, each with a stable type and a versioned payload.

message.received

An inbound SMS or MMS arrives on one of your business numbers.

message.delivered

A carrier returns a delivery receipt for a message you sent.

message.failed

A message could not be delivered — includes a failure reason code.

call.completed

A call ends — fires with direction, duration and the numbers involved.

call.missed

An inbound call rings out without being answered on any device.

contact.created

A contact is added through the API or a synced form submission.

Register a webhook endpoint
curl https://api.netexem.com/v1/webhook_endpoints \
  -H "Authorization: Bearer $NETEXEM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example.com/hooks/netexem",
    "events": ["message.received", "message.delivered", "call.completed"]
  }'
What every webhook gives you

Signed, retried, and typed by default.

Signed

Every request carries an X-Netexem-Signature header — an HMAC-SHA256 of the raw body keyed with your endpoint secret. Verify it before you trust a single field.

Retried

If your endpoint does not return a 2xx, the event is retried with exponential backoff. Respond fast, acknowledge early, and process the payload out of band.

Typed events

A stable event type and a versioned payload shape on every request. Switch on the type, read the data object, and ignore events you have not subscribed to.

FAQ

Webhook questions, answered.

Want to see the signed events flowing into a real endpoint? Book a walkthrough.

How do I verify the webhook signature?

Every request includes an X-Netexem-Signature header containing a timestamp and an HMAC-SHA256 digest. Compute your own HMAC-SHA256 over the timestamp joined to the raw request body, keyed with your endpoint secret, then compare it to the v1 value using a constant-time comparison. Always sign the raw body bytes — re-serializing the parsed JSON can change whitespace and break the match.

Which events fire?

You subscribe to the events you care about when you create an endpoint. The catalog covers inbound messages (message.received), carrier delivery receipts (message.delivered and message.failed) and call events (call.completed, call.missed), plus contact.created. Each event has a stable type and a versioned data payload.

What happens if my endpoint is down?

Return any 2xx status to acknowledge an event. If VanceTel does not get a 2xx, the webhook is retried with exponential backoff over the following hours, so a brief outage does not drop events. Acknowledge quickly and process the payload asynchronously to avoid retries from slow handlers.

Should I also poll the API, or are webhooks enough?

Webhooks are push-based and cover inbound messages, delivery receipts and call events as they happen. The public REST API is write-and-action oriented — you use it to create endpoints, send messages and manage contacts. For most integrations, webhooks deliver the real-time events and the API performs the actions.

Listen for every message, receipt and call.

Wire VanceTel webhooks into your stack and react to phone activity in real time — every event signed, retried and typed.