Configure webhooks

Stables webhooks allow you to receive real-time notifications when events occur in your Stables account. There are two ways to manage webhook subscriptions:

  • Developer Dashboard (recommended) — manage webhook endpoints from the dashboard, with a built-in event catalog, test events, delivery history and manual replay.
  • Webhook API — create and manage subscriptions programmatically. This applies to accounts that haven't yet been migrated to dashboard-managed webhooks.

The event payload structure is identical on both — switching between them doesn't require any changes to how you process events.

Managing webhooks in the Dashboard

Open Settings → Webhooks in the Developer Dashboard to manage your endpoints.

  1. Add an endpoint. Enter the HTTPS URL that should receive events and select the event types you want delivered to it.
  2. Copy the signing secret. Each endpoint has its own signing secret (starting with whsec_), available on the endpoint's detail page. Use it to verify deliveries — see Verifying signatures below.
  3. Send a test event. Use the Testing tab to send example payloads for any event type to your endpoint before going live.
  4. Inspect and replay deliveries. Each endpoint shows a full delivery log with the response your server returned. Failed deliveries can be replayed manually at any time.

Deliveries to dashboard-managed endpoints are retried automatically on an exponential back-off schedule for over a day after the first failure. If every retry fails, the delivery is marked failed and can still be replayed from the dashboard.

Subscribing via the Webhook API

For accounts not yet on dashboard-managed webhooks, create a webhook subscription with the Stables API. The subscription is active as soon as it's created.

Request

curl -X POST https://api.stables.money/api/v1/webhooks \
  -H "X-Api-Key: <api-key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: <idempotency-key>" \
  -d '{
    "name": "Payment Status Notifications",
    "url": "https://your-domain.com/webhooks/stables",
    "eventTypes": [
      "customer.created",
      "kyc_link.updated.status_transitioned",
      "transfer.updated.status_transitioned"
    ],
    "secret": "your_optional_signing_secret"
  }'

Response

{
  "subscription": {
    "subscriptionId": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Payment Status Notifications",
    "url": "https://your-domain.com/webhooks/stables",
    "eventTypes": [
      "customer.created",
      "kyc_link.updated.status_transitioned",
      "transfer.updated.status_transitioned"
    ],
    "active": true,
    "createdAt": "2024-01-15T10:30:00Z",
    "updatedAt": "2024-01-15T10:30:00Z"
  }
}

Receiving Events

When an event occurs, Stables sends a POST to your endpoint URL. The webhook payload follows the same structure regardless of how the subscription was created.

FieldTypeDescription
api_versionstringAPI version used when the event was generated. Currently "v1".
event_idstringUnique UUID for this event. Use as an idempotency key to handle duplicate deliveries.
event_developer_idstringYour tenant ID.
event_categorystringTop-level resource category (e.g. "customer", "virtual_account.activity").
event_typestringFull event type string, e.g. "kyc_link.updated.status_transitioned".
event_object_idstringID of the primary resource.
event_object_statusstring(optional) The new status that triggered the event. Present on status-transition events.
event_objectobjectFull resource snapshot. Shape varies by event_type.
event_created_atstringISO 8601 UTC timestamp when the event was created.

For the full event type catalog and per-event payloads, see the Webhooks documentation in the Developer Platform.

Verifying signatures

Dashboard-managed endpoints

Every delivery includes three headers:

HeaderDescription
svix-idUnique ID for the message. Constant across retries of the same event.
svix-timestampUnix timestamp (seconds) when the delivery was sent.
svix-signatureBase64-encoded HMAC-SHA256 signature(s) of the delivery.

The signature is an HMAC-SHA256 of {svix-id}.{svix-timestamp}.{raw request body} using your endpoint's signing secret. We recommend verifying with the open-source Svix libraries rather than implementing this by hand:

import { Webhook } from "svix";

const wh = new Webhook(process.env.STABLES_WEBHOOK_SECRET); // whsec_...

app.post("/webhooks/stables", (req, res) => {
  try {
    // Verify against the raw request body, not the parsed JSON.
    const event = wh.verify(req.rawBody, req.headers);
    // Process asynchronously after responding.
    res.status(200).send();
  } catch (err) {
    res.status(400).send();
  }
});

Verification also rejects stale timestamps, which protects against replay attacks.

API-managed subscriptions

If you set a secret on the subscription, each delivery includes an X-Webhook-Signature header containing the HMAC-SHA256 of the raw request body with that secret. Reject requests whose signature doesn't match.

Security best practices

  • Verify webhook signatures on every delivery, as described above.
  • Use HTTPS for your webhook URL and ensure the certificate is valid so traffic and signatures can't be tampered with in transit.
  • Store webhook secrets securely (e.g. environment variables or a secrets manager). Don't hardcode them in source.
  • Return 200 quickly so Stables doesn't treat the delivery as failed and retry. Process the event asynchronously after responding.
  • Implement idempotency using event_id so duplicate deliveries or retries don't cause duplicate side effects.
  • Log webhook events (event type, event_id, and outcome) for debugging and monitoring

IP Address

Deliveries to API-managed subscriptions originate from a fixed egress IP address per environment. We recommend adding these addresses to your endpoint's allowlist as an extra layer of security, ensuring that incoming webhooks genuinely originate from Stables:

EnvironmentEgress IP
Production54.253.39.1
Sandbox13.237.39.75

These addresses are stable. In the unlikely event they change, we will notify you in advance.

Dashboard-managed endpoints are delivered from a different, also static, set of IP addresses. Contact us if you need the current allowlist for your environment.

Inspecting Delivery History

Dashboard-managed endpoints show their full delivery history — including your server's response and manual replay — directly in the Settings → Webhooks page.

For API-managed subscriptions, use the delivery history endpoint to inspect past delivery attempts — useful for debugging missed events or understanding retry behaviour. Filter by subscription using the subscriptionId query parameter.

GET /api/v1/webhooks/deliveries

Optional query parameters: subscriptionId, status, eventType, pageSize, pageToken.

The response returns a deliveries array of WebhookDeliveryRecord objects:

FieldTypeDescription
deliveryIdstringUnique ID for this delivery attempt
statusstringPENDING, PROCESSING, RETRYING, SUCCESS, or FAILED
attemptCountnumberNumber of delivery attempts made so far
responseCodenumberHTTP status code returned by your endpoint (if any)
nextRetryAtstringISO 8601 timestamp of the next scheduled retry (if retrying)
createdAtstringWhen the delivery was first attempted

Stables retries failed deliveries on an exponential back-off schedule. Once status reaches FAILED, no further retries are made.

Next Steps

  • Review the Webhooks documentation for the full event type catalog and payload examples
  • Implement proper error handling and retry logic
  • Set up monitoring and alerting for webhook failures

This completes your webhook integration with Stables. Your application will now receive real-time notifications for customer and payment events and can respond accordingly.

On this page