Testing locally
You can test your webhook handler on your own machine using a sandbox endpoint.
You don't need a mock server or fixture files. You can use real event payloads, real signatures, and real retries.
Sandbox is a little more relaxed than production: plain http:// endpoints
are allowed in sandbox. Production requires HTTPS. Everything else works the
same way.
Getting your handler a URL
Pick the setup that matches how you're working.
Running the API locally. The dispatcher runs in the same process, so it can
reach your laptop directly. Register http://localhost:3000/webhooks and you're
ready to go.
Using the hosted sandbox. Deliveries come from our servers, so they need a hostname they can reach. A tunnel is the easiest option:
# ngrok
ngrok http 3000
# or cloudflared
cloudflared tunnel --url http://localhost:3000
Both commands give you a public URL. Register that URL as your webhook endpoint.
Free tunnel URLs usually change when you restart them. Instead of changing the endpoint every time, you can point the tunnel at a small local proxy, or keep a second endpoint registered and switch between them.
Registering the endpoint
Webhook endpoints are managed in Cockpit under Developers → Webhooks. There is no API for creating them.
You'll need to set three things:
- URL — your tunnel URL or
localhost, including the handler path. - Events — use
*while developing. You can narrow this down later. - Description — give it a name that tells you which machine it's for.
Save the endpoint, then copy its signing secret:
export ALETA_WEBHOOK_SECRET=whsec_...
Your handler needs this secret to verify webhook signatures. Signature verification can't be turned off.
Testing webhook event
Click Send test event on the endpoint.
This sends a test delivery directly to that endpoint, so it doesn't matter which events the endpoint is subscribed to.
The button only tells you that the event was queued. The dispatcher should
pick it up within a second or so, and the result will appear in the delivery
log.
The event looks like this:
{
"id": "evt_01M480J2CTN4T8D9E8D2TBN1KC",
"object": "event",
"type": "webhook.test",
"api_version": "2026-08-01",
"environment": "sandbox",
"created_at": "2026-10-06T07:06:30Z",
"data": {
"object": {
"object": "webhook_test",
"endpoint_id": "whe_01HX8ZQ2K4M7P9RSTVWXYZAB",
"message": "This is a test event from AletaPlanet Cockpit.",
"sent_at": "2026-10-06T07:06:30Z"
}
}
}
A test event has a real signature, so it's useful for testing your signature verification.
One thing to remember: you can choose any event type for a test, but
data.object is always a webhook_test object.
For example, if you send a test as card.frozen, data.object.id won't exist.
Use webhook.test to check connectivity and signature verification. Use real
events to test event-specific handlers.
Debugging from the delivery log
Each delivery attempt records what happened:
- HTTP status
- first 8 KB of the response body
- request duration
- transport error, if the request didn't complete
Check the response body when debugging. If your framework returns a stack trace
with a 500, you'll usually find it right there in the delivery log.
Before you go to production
Create a separate production endpoint. Endpoints and signing secrets don't carry over between environments.
Check these things first:
- The production URL uses HTTPS and has a valid certificate.
http://only works in sandbox. - Your handler returns
2xxwithin 10 seconds. If it needs to do real work, put that work behind a queue. - Your handler returns
2xxfor event types it doesn't recognise. A4xxcauses retries and counts toward the 20 consecutive failures that disable an endpoint. - Deduplication uses
event.id. Test it by replaying a delivery.
Keep the sandbox endpoint registered. It doesn't cost anything, and it'll be useful the next time you change your handler.
Common issues
| Symptom | Cause |
|---|---|
| Delivery log shows a transport error, but your handler gets nothing | The tunnel is down, or the endpoint still points to an old tunnel URL |
signature does not match on every delivery | JSON middleware changed the body before you verified it |
| Signature fails only for an old saved payload | The 5-minute timestamp window has expired. Sign it again |
| Test events arrive, but real events don't | The endpoint isn't subscribed to those events. Test events are sent directly to the endpoint |
data.object.id is undefined on a test event | Test events always contain a webhook_test object |
| Endpoint stopped receiving events | 20 consecutive failures disabled it. Re-enable it and reconcile the missing period through the API |
| Handler runs twice for one action | Two endpoints point to the same service, or the delivery was retried |
Next
- Signature verification — the full verifier in several languages.
- Card issuing events — every event type and what its payload looks like.