StableOps
Concepts

Webhooks

Real-time notifications for payment events and system alerts.

Webhooks allow your application to receive real-time notifications when events occur in StableOps, such as when a payment is detected, confirmed, or finalized.

Overview

Instead of polling the API to check payment status, StableOps sends HTTP POST requests to your server when events happen. This enables:

  • Real-time updates: Know immediately when payments arrive
  • Reduced API calls: No need to poll for status changes
  • Reliable delivery: Automatic retries with exponential backoff
  • Event history: All webhook deliveries are logged and can be replayed

How Webhooks Work

┌─────────────┐         ┌─────────────┐         ┌─────────────┐
│  Blockchain │────────▶│  StableOps  │────────▶│  Your App   │
└─────────────┘         └─────────────┘         └─────────────┘
   Payment sent          Event detected         Webhook sent
  1. An event occurs (e.g., payment detected on blockchain)
  2. StableOps creates a webhook delivery
  3. HTTP POST request is sent to your endpoint
  4. Your server processes the event and returns 200 OK
  5. If delivery fails, StableOps retries with exponential backoff

Webhook Events

Payment Events

payment_order.created

Sent when a new payment order is created.

payment.detected

Sent when a payment transaction is first seen by the scanner. This can lag the chain head, so don't assume exactly 0 confirmations at this point.

Action: Update UI to show "Payment received, confirming..."

Warning: Do not fulfill the order yet - the transaction could still be reverted.

payment.confirmed

Sent when the payment has received sufficient on-chain confirmations. The transaction is likely final but has not yet reached the chain's finality guarantee.

Action: Safe to update internal systems and notify the user, but consider waiting for payment.finalized before releasing goods.

payment.finalized

Sent when the payment has reached finality and cannot be reverted.

Action: Safe to fulfill the order - payment is guaranteed.

This is the recommended event to trigger order fulfillment.

payment.expired

Sent when an order passes its expiration time without matching any transfer.

Action: Mark the order as expired and prompt the user to start over.

payment.reverted

Sent when a previously detected payment is rolled back. The receipt failed, or after a reorg the block hash no longer matches the stored event.

Action: Undo any optimistic handling done on payment.detected / payment.confirmed. This is why we recommend fulfilling on payment.finalized.

payment_order.canceled

Sent when an order still in created (before any deposit is detected) is canceled via the cancel endpoint.

Action: Mark the order as canceled and release any resources reserved for it.

Note: Even though cancellation is caller-initiated and the HTTP response already returns the result, this event is still emitted, so integrators that treat the webhook stream as the source of truth (event sourcing) never miss the terminal transition. Endpoints that don't care can simply ignore it.

See the Payment Events API reference for the full payload schemas.

Merchant Subscription Events

StableOps also emits events for merchant-managed end-user subscriptions and their invoices. Use these events to activate accounts, track renewals, suspend overdue users, and reconcile invoice payment outcomes.

EventTrigger
end_user_subscription.createdA merchant subscription was created
end_user_subscription.activatedThe first invoice was paid and the subscription became active
end_user_subscription.renewedA renewal invoice was paid and the billing period advanced
end_user_subscription.canceledThe subscription was canceled immediately
end_user_subscription.expiredThe subscription expired after cancellation or unpaid invoices
end_user_subscription.past_dueThe subscription entered the past-due state
end_user_invoice.openA new subscription invoice was issued
end_user_invoice.paidA subscription invoice was paid
end_user_invoice.payment_failedA subscription invoice payment failed
end_user_invoice.payment_lateA payment arrived after the invoice was already uncollectible

Subscription invoice checkout still creates payment orders, so you may also receive the normal payment events for the underlying order. Treat the end_user_invoice.* and end_user_subscription.* events as the source of truth for subscription state.

See the Subscription Events API reference for the full payload schemas.

Operational & Agent Events

Beyond payment events, StableOps also pushes the following. In the dashboard these form the Operational events subscription group. A single toggle covers all of them, separate from the payment-events group:

EventTrigger
address.pool.lowThe available receiving-address pool for a chain is low
agent.action.requestedAn agent requested a write action that needs approval
agent.action.approvedA pending agent action was approved
agent.action.executedAn approved agent action finished executing

See the Operational Events API reference for the full payload schemas.

Setting Up Webhooks

1. Create an Endpoint

In your StableOps dashboard:

  1. Go to Webhooks in the sidebar
  2. Click Add Endpoint
  3. Enter your webhook URL (use HTTPS in production for security)
  4. Select which events to receive
  5. Save the webhook secret

The secret field is returned only on create and rotate. Store it somewhere durable; the dashboard will not show it again. After you call rotate-secret, the previous secret stays valid alongside the new one for 24 hours, so you can roll out the new value without dropping deliveries.

2. Verify Signatures

Always verify webhook signatures to ensure requests are from StableOps.

import { SIGNATURE_HEADER, verifySignature } from '@stableops/api-sdk/webhooks'

const result = verifySignature({
  secrets: [webhookSecret],
  header: request.headers.get(SIGNATURE_HEADER) ?? undefined,
  rawBody,
})

if (!result.ok) {
  console.error('Invalid signature:', result.reason)
  // Reject the request
}

Delivery, retries, and the dead letter queue

Success criteria

Any 2xx response is treated as success. Any other status code, a network error, or no response within 10 seconds counts as failure.

Retry schedule

Failed deliveries are rescheduled on this curve before moving to the dead letter queue on attempt 6:

AttemptDelay
130 s
260 s
35 min
430 min
52 h
6+DLQ

Delivery log fields

Key fields you'll see on webhook-deliveries:

FieldDescription
attemptsNumber of HTTP attempts performed
response_statusDownstream status code on the most recent attempt
response_duration_msHow long the last attempt took
error_messageShort, log-friendly reason for the most recent failure
next_retry_atWhen the worker will retry (null once delivery is terminal)
statuspending, succeeded, failed, dead_letter

Replay

The three replay endpoints (endpoint replay, single-delivery replay, and replay-dead-letters) all enqueue a brand-new delivery row. The original audit log is preserved. The dashboard's "replay" button is just these endpoints under the hood.

Best Practices

1. Always Verify Signatures

Reject any request that doesn't carry a valid X-Product-Signature. Without verification, anyone who discovers your endpoint URL can forge events.

2. Return 200 Quickly

Return 200 as soon as you've verified the signature and recorded the event id. Heavy processing (fulfillment, notifications, ledger updates) should happen asynchronously so delivery doesn't time out and trigger a retry.

3. Process Idempotently

Store X-Event-Id before mutating your own state. Network retries and platform replays can deliver the same event more than once; applying it twice must be safe.

4. Use Raw Body for Verification

Compute the signature over the exact bytes you received, before any JSON parsing or re-serialization. Frameworks that parse and re-stringify the body change it and break the signature.

5. Handle All Event Types

Subscribe to and handle every event you might receive, including payment.reverted, payment.expired, and subscription events such as end_user_invoice.paid / end_user_subscription.renewed. Unhandled events are still counted as successful deliveries but your order and subscription tracking will drift.

6. Log Everything

Record each delivery's X-Event-Id, X-Delivery-Id, event type, and your processing result. Good logs are the fastest way to debug missed or duplicate fulfillment.

7. Monitor Delivery Health

Check the delivery log for endpoints with a rising failed or dead_letter count. Set up alerts for endpoints that haven't returned 2xx in the last hour, and use the replay endpoint to drain the DLQ after fixing the issue.

Next Steps

How is this guide?

Last updated

On this page