Skip to content
Markets open
Quickstart · Webhooks and agents

Receive webhooks and verify signatures

Every delivery is signed with your endpoint's secret and a timestamp. Check the signature over the raw body, refuse anything older than five minutes, and answer quickly with a 2xx.
Calls
X-PX-Signature · X-PX-Timestamp · X-Webhook-Event · X-Webhook-Id
Cost
Free.

Get an API key

Open an account, then create a key under Dashboard, API keys. Live keys (wmmn_live_sk_) send real traffic and charge your balance. Test keys (wmmn_test_sk_) run the same request through routing and pricing without delivering it, and draw on test credit, which comes with an invite code.

Create webhook endpoints in the dashboard under Webhooks. API keys cannot create or edit them, so a leaked key can never redirect your events. The signing secret is shown once when you create the endpoint.

Fund the balance

PacketExchange is prepaid. Top up from $5 by card or crypto, or from $100 by wire, under Dashboard, Billing. An agent can top itself up with x402. There is no contract and no monthly fee.

Nothing to fund. Webhooks cost nothing to receive.

CostFree.

Make the request

X-PX-Signature is v1= followed by the hex HMAC-SHA256 of "<X-PX-Timestamp>.<raw body>", keyed with your secret. Verify it on the exact bytes you received, before parsing the JSON. The cURL tab sends your receiver a correctly signed test delivery.

# Send your receiver a signed test delivery, exactly as PacketExchange signs one:
# X-PX-Signature: v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">
body='{"event":"ping","data":{},"timestamp":"2026-01-01T00:00:00.000Z"}'
ts=$(date +%s)
sig=$(printf '%s.%s' "$ts" "$body" | openssl dgst -sha256 -hmac "$PACKETEXCHANGE_WEBHOOK_SECRET" | sed 's/^.* //')

curl -i http://localhost:3000/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Event: ping" \
  -H "X-PX-Timestamp: $ts" \
  -H "X-PX-Signature: v1=$sig" \
  --data-raw "$body"
Save as request.sh, run bash request.shNeeds curl, plus jq for the multi-step samplesSet PACKETEXCHANGE_WEBHOOK_SECRET to the endpoint's signing secret. The receivers listen on port 3000.

Handle the response

Each delivery is a POST with a JSON body of the event name, its data and the time it happened.

POST to your endpointevent sms.sent
{
  "event": "sms.sent",
  "data": {
    "messageId": "<message id>",
    "to": "+447700900123",
    "from": "Acme",
    "status": "accepted",
    "segments": 1,
    "cost": "<USD, 6 decimals>",
    "submittedAt": "<ISO-8601 UTC>"
  },
  "timestamp": "<ISO-8601 UTC>"
}

Values in angle brackets are placeholders for your own ids, times and amounts.

X-PX-Timestamp
Unix seconds when this attempt was sent. Reject it when it is more than 5 minutes from now.
X-PX-Signature
v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">. Compare in constant time.
X-Webhook-Id
The delivery id, to de-duplicate retries.
X-Webhook-Signature
The legacy body-only signature, still sent during the transition. Prefer v1.

When something goes wrong

  • Answer with any 2xx within 10 seconds, then do slow work afterwards. A failed delivery is retried up to 5 times.
  • Every delivery is in the dashboard's delivery log, where you can resend any your endpoint missed.
  • Events include sms.sent, call.completed, number.sms.received, number.call.received, topup.confirmed and balance.low.

Go further

Start building

Run it with your own key

Open an account, add credit from $5 and create a key. You pay the carrier's rate plus a fee capped at $0.001.