Webhook endpoints
CRUD for webhook subscriptions. Each endpoint is bound to one (merchant, env), sandbox + live environments have separate endpoints.
For the actual signing scheme + verification examples, see Webhooks.
The webhook_endpoint object
{
"object": "webhook_endpoint",
"id": "ckwhe_xxxxxxxxxxxx",
"url": "https://your-app.com/webhooks/swappr",
"events": ["payout_paid", "payout_failed", "wallet_funded"],
"is_active": true,
"env": "live",
"last_success_at": "2026-05-05T12:34:56Z",
"last_failure_at": null,
"consecutive_failures": 0,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-05-05T12:34:56Z"
}POSTCreate a webhook endpoint
The signing secret is returned ONCE in the response body. Store it before discarding the response, it can’t be retrieved later, only rotated.
Creating an endpoint is idempotent on the Idempotency-Key, a same-key + same-body retry returns the original endpoint (including its secret) with 200; same key + different body returns 409 idempotency_key_conflict.
Must be HTTPS. HTTP is rejected.
Non-empty array of event types
curl https://api.swappr.me/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/swappr",
"events": ["payout_paid", "payout_failed", "wallet_funded"]
}'{
"object": "webhook_endpoint",
"id": "ckwhe_xxxxxxxxxxxx",
"url": "https://your-app.com/webhooks/swappr",
"events": ["payout_paid", "payout_failed", "wallet_funded"],
"is_active": true,
"env": "test",
"secret": "whsec_test_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345",
"created_at": "2026-05-05T12:34:56Z",
...
}GETList webhook endpoints
1-100, default 50
Cursor
Cursor-paginated. The signing secret is never returned on list/retrieve, only on create + rotate.
curl https://api.swappr.me/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_..."GETRetrieve a webhook endpoint
Returns the endpoint without the signing secret.
curl https://api.swappr.me/api/v1/webhook_endpoints/ckwhe_xxx \
-H "Authorization: Bearer sk_test_..."PATCHUpdate a webhook endpoint
Narrow update: url / events / is_active only.
Pass the fields you want to change; others are left as-is.
200 OK with the updated endpoint.
{
"events": ["payout_paid", "payout_failed", "wallet_funded", "customer_verified", "wallet_auto_converted"],
"is_active": true
}DELDelete a webhook endpoint
Permanently removes the endpoint + cascades all delivery history. Cannot be undone.
curl https://api.swappr.me/api/v1/webhook_endpoints/ckwhe_xxx \
-X DELETE \
-H "Authorization: Bearer sk_test_..."{
"object": "webhook_endpoint_delete_result",
"id": "ckwhe_xxxxxxxxxxxx",
"deleted": true
}POSTSend a test event
Fires a synthetic payout_paid-shaped event with _test: true marker. Verifies your signature handler + receiver are wired correctly. Common during integration setup or after rotating a signing secret.
Headers on the synthetic event
X-Swappr-Signature: t=<unix-seconds>,v1=<hex-hmac-sha256>
X-Swappr-Event: payout_paid
X-Swappr-Test: true
User-Agent: Swappr-Webhooks/1.0200 OK on success. 502 Bad Gateway if your receiver returned non-2xx OR a network error. 400 Bad Request if the endpoint is disabled (is_active=false), re-enable via PATCH first.
{
"_test": true,
"event": "payout_paid",
"data": {
"reference": "test_<timestamp>",
"amount_minor": "100000",
"currency": "NGN",
"recipient_name": "TEST RECIPIENT",
"bank_code": "044",
"timestamp": "2026-05-05T12:34:56Z"
}
}{
"object": "webhook_test_result",
"endpoint_id": "ckwhe_xxx",
"delivery_id": "ckwhd_xxx",
"status": "delivered",
"response_status": 200,
"attempts": 1
}{
"error": {
"type": "provider_error",
"code": "delivery_failed",
"message": "Receiver returned non-2xx status: 500."
}
}Best practices
- Use separate endpoints for sandbox + live: they’re env-scoped, so don’t try to share. Different secrets, different delivery histories.
- Subscribe to only the events you handle: un-subscribed events still fire 0 webhooks. Don’t list events your handler doesn’t process, saves you from accidentally returning 5xx and burning retry budget.
- Rotate signing secrets periodically: once per quarter is reasonable. Use the dashboard’s rotate flow; old + new both work for 60 minutes during the cutover.
- Test before going live: call
/testendpoint as part of your CI/CD pipeline post-deploy, so you know the receiver is wired correctly.