Search docs

Search the API documentation

Webhook Best Practices

Follow these practices to build a reliable and secure webhook handler.

Use the raw request body

Always verify the signature against the raw bytes — never re-serialize parsed JSON. JSON serialization is not deterministic and will produce a different signature. In Express, use express.raw() before any JSON parsing middleware.

Use timing-safe comparison

Never use plain string equality to compare signatures — it leaks timing information. Use timingSafeEqual (Node.js), hmac.compare_digest (Python), hash_equals (PHP), or hmac.Equal (Go).

Make handlers idempotent

Failed deliveries are retried automatically (1m, 5m, 30m, 2h, 6h — up to 6 attempts) and can also be retried manually from the dashboard, so the same event may arrive more than once. Every retry carries the same event id (also sent as X-Artos-Event-Id). Store processed IDs and skip duplicates before processing.

Trust the latest status

A payer can retry on the hosted payment page after a failed or expired attempt, so you may receive a payment.failed followed later by a payment.success for the same order. If in doubt, confirm with GET /v1/payments/:id.

Respond quickly

Your endpoint must respond with a 2xx status within 10 seconds. Return 200 OK immediately and process the event asynchronously via a job queue if your logic takes longer. Timeouts and 5xx/408/429 responses are retried; other non-2xx responses (including redirects, which are not followed) are final, so only return 4xx when you really want to reject the event.

Use HTTPS

Use a public HTTPS URL for your webhook endpoint (plain HTTP is accepted but not recommended). URLs pointing to localhost or private IP addresses are rejected.