”Every request fails signature verification”
By far the most common issue, and almost always the same root cause: the body you hash isn’t the body we hashed.Diagnosis
Add a temporary log on your server:Fix by framework
”Most requests verify but some sporadically fail”
Two likely culprits:- Server clock skew. If your server’s clock drifts more than ~5 minutes from real UTC, every delivery looks “stale” and your timestamp check rejects it. Run
ntpd/chronydand confirm withdate -u. - You’re loading the wrong secret in some environments. Log
SECRET.length === 64on startup — if it’s ever false, you’ve loaded an env var from the wrong source.
”I never receive anything”
Walk through this checklist in order:1
Confirm the webhook is registered
2
Confirm the URL is reachable from the public internet
https://, resolves to a public address, and doesn’t redirect — otherwise the URL guard drops every delivery before it leaves our worker. See “Every delivery is dropped immediately” below.3
Confirm the platform is enabled and connected
4
Confirm the message is actually inbound to your project
Send the test message from a phone number that isn’t your own to the line attached to the project.
5
Look at our delivery attempts
We can confirm whether deliveries left our worker via support — include the webhook id, the approximate timestamp, and the URL. We log every delivery attempt with structured fields.
”Every delivery is dropped immediately”
The webhook is registered and the platform is sending events, but nothing reaches your server — and our logs show the delivery never left the worker. This is almost always the URL guard: we validate the destination before every attempt and fail closed, so a URL that doesn’t meet the delivery requirements drops every event without a singlePOST. Registration doesn’t catch this — it only checks URL syntax — so the webhook looks healthy in the list.
Check the registered URL against each rule:
All of these drop the event as fatal — no retry. There’s no update endpoint, so fix the URL by deleting and re-registering (see Managing webhooks); the next event will deliver. Full contract: Delivery → Where we won’t deliver.
”I receive duplicates”
This is expected behavior under at-least-once delivery. The two scenarios that cause it:- Your handler succeeded but timed out before responding. We retried, you processed twice.
- Your handler returned
5xxafter partially processing. We retried, you re-ran the partial work.
Fix
Dedupe at the top of your handler usingX-Spectrum-Webhook-Id plus payload.message.id as a composite key:
”Deliveries time out”
If you’re seeing your endpoint logged as “took >30s,” it triggers a retry on our side and a likely duplicate processing on yours.Diagnosis
Look at what your handler is doing synchronously:Fix
Acknowledge first, process asynchronously:”ngrok URL keeps changing”
Free ngrok tunnels get a new URL every restart. That URL won’t be registered with us, so deliveries 404 immediately.Fix
-
For local development, kill the old webhook and re-register every time you restart ngrok:
- Better: use a reserved subdomain (paid plan) so the URL is stable across restarts.
- Best for long-lived dev: deploy a tiny forwarder (Cloudflare Worker, Vercel function) that POSTs to your local machine over a stable tunnel.
”The signing secret leaked”
Treat it like any other credential leak.- Rotate the secret using the delete-and-recreate flow in Managing webhooks.
- If you suspect the project credentials also leaked, rotate them too:
photon projects regenerate-secret <id>. - Audit recent inbound events for anomalies (events for spaces you don’t recognize, suspicious sender ids, payloads with unusual content).
”I want to test verification without spamming real messages”
Build a test request locally that mimics a real delivery:test-verify.ts
bun test-verify.ts), copy the printed curl, and paste it to test your verifier offline.
”How do I see what we sent you?”
We don’t currently expose a delivery log to customers. If you need to debug a specific event, the fastest path is:- Reach out to support with the webhook id, the approximate UTC timestamp, and what you observed on your side.
- We can confirm whether the delivery left our worker, what status code came back, and how many retries it took.
”Multiple webhook URLs receive the event in different orders”
That’s expected. Deliveries to multiple URLs run in parallel — there’s no ordering guarantee across URLs. If your downstream consumers need to coordinate, designate one URL as the primary and have it fan out internally rather than registering several with us.Still stuck?
Send us:- Your project id (the one in
photon projects show). - The webhook id or the URL.
- A timestamp range (UTC).
- One example body and signature you couldn’t verify (with the secret redacted).