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"],
"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.