Customer Webhooks
Register the HTTPS endpoint the platform pushes events to—including the withdrawal and transfer verification codes—then verify it.
The platform pushes events to one HTTPS endpoint per customer. That's also the only place verification codes arrive: when a user starts a withdrawal or a transfer, the code to confirm it goes to your webhook and is never returned by the API. Register the endpoint here, add the auth headers your backend expects, and send a test notification.
Authentication: every endpoint on this page takes your dashboard session token as a Bearer token. The session must own the {customer_id} in the path; any other id answers 404 CUSTOMER_NOT_FOUND.
Authorization: Bearer YOUR_SESSION_TOKENOne webhook per customer
The webhook endpoint is a singleton at /v1/customers/{customer_id}/webhooks: read it with GET, create or replace it with PUT, change part of it with PATCH, and test it with POST …/verify. Each call returns the endpoint. The URL must be HTTPS on a public host—http, localhost, .local and .internal hosts, private and link-local addresses, and URLs with credentials (user:pass@) are rejected, and redirects aren't followed. Header keys are stored lowercase and header values are never returned: value_masked shows at most a 4-character hint.
Responses use the standard envelope and error format. POST and PATCH accept an optional x-idempotency-key header.
Webhook Fields
| Field | Type | Description |
|---|---|---|
| object | string | Always webhook_endpoint. |
| customer_id | string | Owning customer. |
| url | string | null | Target URL, or null when cleared. |
| headers | array | [{key, value_masked}]. Keys are lowercase. |
| is_enabled | boolean | true while a URL is set. |
| last_verification | object | null | Last test: {is_successful, status_code, message, verified_at}. Reset to null when the URL changes. |
| created_at / updated_at | string | UTC timestamps without milliseconds. |
| created_by / updated_by | string | null | Actor that created or last changed the endpoint. |
Create or Replace Webhook
Set the endpoint. The first call answers 201 Created; later calls replace the URL and the whole header list and answer 200 OK.
POST /v1/customers/{customer_id}/webhooks still works as a deprecated alias of this call.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | HTTPS URL on a public host. |
| headers | array | No | Headers sent with every delivery (below). |
headers[] object
| Field | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | Header name: letters, digits, -, _. Stored lowercase. |
| value | string | Yes | Header value. Write-only. |
{
"url": "https://your-domain.com/webhook/sovera",
"headers": [
{ "key": "Authorization", "value": "Bearer token123" },
{ "key": "X-API-Key", "value": "secret-key-value" }
]
}Success Response (201 Created, or 200 OK on replace):
{
"success": true,
"data": {
"object": "webhook_endpoint",
"customer_id": "corp-redundant-firewall",
"url": "https://your-domain.com/webhook/sovera",
"headers": [
{ "key": "authorization", "value_masked": "Bear********" },
{ "key": "x-api-key", "value_masked": "secr********" }
],
"is_enabled": true,
"last_verification": null,
"created_at": "2026-10-12T14:20:00Z",
"updated_at": "2026-10-12T14:20:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T14:20:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X PUT "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d '{
"url": "https://your-domain.com/webhook/sovera",
"headers": [
{ "key": "Authorization", "value": "Bearer token123" },
{ "key": "X-API-Key", "value": "secret-key-value" }
]
}'Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND not owned by the session, or deleted · 422 VALIDATION_ERROR URL not HTTPS or not a public host (source.pointer: /url), or invalid header key.
Get Webhook
Return the endpoint with masked header values and the last test result.
GET https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooksSuccess Response (200 OK):
{
"success": true,
"data": {
"object": "webhook_endpoint",
"customer_id": "corp-redundant-firewall",
"url": "https://your-domain.com/webhook/sovera",
"headers": [
{ "key": "authorization", "value_masked": "Bear********" },
{ "key": "x-api-key", "value_masked": "secr********" }
],
"is_enabled": true,
"last_verification": {
"is_successful": true,
"status_code": 200,
"message": "Webhook test successful",
"verified_at": "2026-10-12T14:25:00Z"
},
"created_at": "2026-10-12T14:20:00Z",
"updated_at": "2026-10-12T14:20:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T14:26:10Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X GET "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooks" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 WEBHOOK_NOT_FOUND no webhook configured.
Update Webhook
Change part of the endpoint. headers, when sent, replaces the whole list. "url": null clears the target and stops deliveries (is_enabled: false); to clear it entirely, send {"url": null, "headers": []} (GET then returns it with url: null and is_enabled: false). Changing the URL resets last_verification.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | null | No | New HTTPS URL on a public host, or null to clear. |
| headers | array | No | New header list ([{key, value}]). |
{
"url": "https://your-domain.com/webhook/v2/sovera",
"headers": [{ "key": "X-API-Key", "value": "rotated-secret-key" }]
}Success Response (200 OK):
{
"success": true,
"data": {
"object": "webhook_endpoint",
"customer_id": "corp-redundant-firewall",
"url": "https://your-domain.com/webhook/v2/sovera",
"headers": [{ "key": "x-api-key", "value_masked": "rota********" }],
"is_enabled": true,
"last_verification": null,
"created_at": "2026-10-12T14:20:00Z",
"updated_at": "2026-10-12T15:30:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T15:30:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X PATCH "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooks" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-d '{ "url": "https://your-domain.com/webhook/v2/sovera", "headers": [{ "key": "X-API-Key", "value": "rotated-secret-key" }] }'Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 WEBHOOK_NOT_FOUND · 422 VALIDATION_ERROR invalid URL or header.
Verify Webhook
Send a test notification with your real header values and record the result in last_verification. The call answers 200 OK whether your endpoint accepts it or not; your endpoint's response body is never echoed. Send no body.
The test notification your endpoint receives looks like this—ignore anything with "test": true:
{
"test": true,
"pin": "123456",
"atx_id": "test-transaction-id",
"withdrawal_context": {
"user_id": "test-user",
"sub_account_id": "corp-redundant-firewall",
"currency": "BTC",
"amount": "0.001",
"status": "pin_sent",
"pin_sent_at": "2026-10-12T14:25:00.000Z"
},
"timestamp": "2026-10-12T14:25:00.000Z"
}POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooks/verifySuccess Response (200 OK), endpoint answered 401:
{
"success": true,
"data": {
"object": "webhook_endpoint",
"customer_id": "corp-redundant-firewall",
"url": "https://your-domain.com/webhook/sovera",
"headers": [
{ "key": "authorization", "value_masked": "Bear********" },
{ "key": "x-api-key", "value_masked": "secr********" }
],
"is_enabled": true,
"last_verification": {
"is_successful": false,
"status_code": 401,
"message": "The endpoint responded with HTTP 401.",
"verified_at": "2026-10-12T14:25:00Z"
},
"created_at": "2026-10-12T14:20:00Z",
"updated_at": "2026-10-12T14:20:00Z",
"created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
"updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
},
"meta": {
"timestamp": "2026-10-12T14:25:00Z",
"version": "v1",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/webhooks/verify" \
-H "Authorization: Bearer YOUR_SESSION_TOKEN"Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 WEBHOOK_NOT_FOUND no webhook URL configured · 422 VALIDATION_ERROR the stored URL no longer resolves to a public host.
Event deliveries
Once the endpoint is set, it receives every event as a POST with your configured headers. The Webhooks page describes the envelope, retries, signature, how to process events safely, and the full event catalogue, including the verification code events.
Deprecated routes
These still answer for one release. Move to the routes above.
| Deprecated | Use instead | Behavior today |
|---|---|---|
POST /v1/customers/{customer_id}/webhooks | PUT …/webhooks | Same create-or-replace. |
DELETE /v1/customers/{customer_id}/webhooks | PATCH …/webhooks with {"url": null, "headers": []} | Clears URL and headers and answers 204. |