API referenceWebhook endpoints

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"
}

Create a webhook endpoint

POST /v1/webhook_endpoints

⚠️

The signing secret is returned ONCE in the response body. Store it before discarding the response — it can’t be retrieved later, only rotated.

Request

{
  "url": "https://your-app.com/webhooks/swappr",
  "events": ["payout_paid", "payout_failed", "wallet_funded"]
}
FieldRequiredNotes
urlyesMust be HTTPS. HTTP is rejected.
eventsyesNon-empty array of event types

Example

curl https://api.swappr.me/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"]
  }'

Response

{
  "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",
  ...
}

List webhook endpoints

GET /v1/webhook_endpoints

ParamNotes
limit1-100, default 50
starting_afterCursor

Cursor-paginated. The signing secret is never returned on list/retrieve — only on create + rotate.


Retrieve a webhook endpoint

GET /v1/webhook_endpoints/{id}

Returns the endpoint without the signing secret.


Update a webhook endpoint

PATCH /v1/webhook_endpoints/{id}

Narrow update — url / events / is_active only.

Request

{
  "events": ["payout_paid", "payout_failed", "wallet_funded", "customer_verified"],
  "is_active": true
}

Pass the fields you want to change; others are left as-is.

Response

200 OK with the updated endpoint.


Delete a webhook endpoint

DELETE /v1/webhook_endpoints/{id}

Permanently removes the endpoint + cascades all delivery history. Cannot be undone.

curl https://api.swappr.me/v1/webhook_endpoints/ckwhe_xxx \
  -X DELETE \
  -H "Authorization: Bearer sk_test_..."

Response

{
  "object": "webhook_endpoint_delete_result",
  "id": "ckwhe_xxxxxxxxxxxx",
  "deleted": true
}

Send a test event

POST /v1/webhook_endpoints/{id}/test

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.0

Body

{
  "_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"
  }
}

Response

200 OK on success:

{
  "object": "webhook_test_result",
  "endpoint_id": "ckwhe_xxx",
  "delivery_id": "ckwhd_xxx",
  "status": "delivered",
  "response_status": 200,
  "attempts": 1
}

502 Bad Gateway if your receiver returned non-2xx OR a network error:

{
  "error": {
    "type": "provider_error",
    "code": "delivery_failed",
    "message": "Receiver returned non-2xx status: 500."
  }
}

400 Bad Request if the endpoint is disabled (is_active=false) — re-enable via PATCH first.


Best practices

  1. Use separate endpoints for sandbox + live — they’re env-scoped, so don’t try to share. Different secrets, different delivery histories.
  2. 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.
  3. 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.
  4. Test before going live — call /test endpoint as part of your CI/CD pipeline post-deploy, so you know the receiver is wired correctly.