Testing webhooks
Send test events, resend deliveries, read the delivery log and receive events on a development machine.
Send a test event
A test event is a ping event sent to one endpoint at once, in a single attempt. Its data.object is the endpoint itself. Use it to check reachability and your signature code.
- Dashboard: Developers → Webhooks → Send test. It shows the HTTP status your endpoint answered.
- API:
const delivery = await zp.webhookEndpoints.sendTest('we_2Lk9Qm4Xz7Pv1Rt8Wb3Ns6Hd');
console.log(delivery.status, delivery.last_status_code, delivery.last_error);The response is the delivery, with your endpoint's answer. ping is sent whatever the endpoint subscribes to, and never on its own: only on request. Your handler should answer 2xx to it and ignore it. A disabled endpoint answers 409 webhook_endpoint_disabled: enable it first.
Test real events
Test mode produces real events from real actions on the testnet:
| To get | Do |
|---|---|
payment_intent.created, payment_intent.canceled | Create a payment intent, then cancel it. |
terminal.created, terminal.deactivated | Add a testnet terminal in the dashboard, then deactivate it. |
payment_intent.succeeded, payment.succeeded | Take a payment on a testnet terminal with a testnet pass. |
payment_intent.payment_failed | Tap a pass whose spending limit is below the amount, or a frozen pass. |
payment_intent.requires_action | A payment the risk policy holds for confirmation, for example above the confirmation threshold. |
refund.created, refund.succeeded, payment.refunded | Refund a test payment. |
See the testing guide for what a sandbox payment needs.
Resend a delivery
Resend any delivery from the log (Resend in the dashboard, or POST /v1/webhook_deliveries/{id}/retry). ZyloPay sends the same event again now, as a new delivery with one attempt, freshly signed. Use it after fixing a bug in your handler.
Replay events locally
Every event is available for 30 days from GET /v1/events. Feed them to your handler in a test, or pipe one into a local request. The body must be signed, so sign it with your test secret the way ZyloPay does:
body=$(curl -s https://zylopay-api.fly.dev/api/v1/events/evt_… -H "Authorization: Bearer $ZYLOPAY_SECRET_KEY")
t=$(date +%s)
sig=$(printf '%s' "$t.$body" | openssl dgst -sha256 -hmac "$ZYLOPAY_WEBHOOK_SECRET" -r | cut -d' ' -f1)
curl -s http://localhost:4242/ -H "ZyloPay-Signature: t=$t,v1=$sig" -H "Content-Type: application/json" --data-binary "$body"Sign test payloads in unit tests
With the Node SDK, build a valid header for any payload and secret:
import ZyloPay from '@zylopay/node';
const payload = JSON.stringify({ id: 'evt_test', object: 'event', type: 'payment.succeeded', data: { object: {} } });
const header = ZyloPay.webhooks.generateTestHeaderString({ payload, secret: 'whsec_test_secret' });
const event = ZyloPay.webhooks.constructEvent(payload, header, 'whsec_test_secret');On a development machine
ZyloPay only delivers to public addresses. Expose your local port with a tunnel (cloudflared tunnel --url http://localhost:4242, ngrok http 4242, or similar) and register the tunnel URL on a test endpoint. Test endpoints may use http; live endpoints must use https.
Checklist before going live
- Signature verification on the raw body, with a 5-minute tolerance.
- Deduplication on the event ID.
2xxwithin 10 seconds, heavy work in a queue.- An alert when
failing_sinceis set on a live endpoint. - A scheduled catch-up from
GET /v1/events.