SUQODocs
User GuideAPI ReferenceSDKs

Webhook events

The event catalogue and wire format. To set an endpoint up see Receive webhook events; to check a delivery is genuine see Verify a webhook signature.

Events

EventSent when
checkout.succeededA checkout payment on your account was finalized successfully
checkout.failedA checkout payment failed terminally (no further retry by SUQO)
subscription.status_changedA subscription moved from one status to another

checkout.succeeded

{
  "event": "checkout.succeeded",
  "subscription_id": "8f3c2b10-4d5e-4a91-9b77-1c2d3e4f5a6b",
  "amount": "1500.00",
  "status": "succeeded"
}

status is always "succeeded". Sent once, after the payment is committed on our side.

checkout.failed

{
  "event": "checkout.failed",
  "subscription_id": "8f3c2b10-4d5e-4a91-9b77-1c2d3e4f5a6b",
  "amount": "1500.00",
  "status": "failed"
}

status is always "failed". We intentionally do not expose the specific cause (gateway decline, verification error, timeout) — that detail stays in SUQO's logs.

Pending or transient failures do not produce a webhook. We keep retrying the payment internally and only emit checkout.failed once the outcome is final.

Parse amount as a decimal. It is sent as a string to avoid float rounding — do not read it into a float.

subscription.status_changed

{
  "event": "subscription.status_changed",
  "subscription_id": "c5d91b6d-8948-4e35-bc55-da60b411ff51",
  "previous_status": "inactive",
  "current_status": "active",
  "changed_at": "2026-08-17T10:24:03.381452+00:00"
}
FieldNotes
subscription_idUUID of the subscription that moved.
previous_status / current_statusBoth drawn from the subscription status set — see Subscription statuses.
changed_atISO 8601, UTC, microsecond precision, explicit offset.

Each delivery describes one transition, so a single subscription produces several of these across its life — activation after the first payment, activedue when a renewal comes round, → cancelled when it ends. Branch on the pair, not on the event name alone: inactiveactive is a reactivation, pending_checkoutactive is a first activation, and the two usually deserve different handling on your side.

Two things this event is not:

  • It is not a payment signal. There is no amount and no payment outcome in the body. A subscription reaching active does not by itself mean money arrived — checkout.succeeded is the event that says that.
  • It is not ordered. Deliveries are retried independently, so a later transition can land before an earlier one. Use changed_at to decide which is newer — see Retries.

Request format

Every delivery is a POST with:

HeaderValue
Content-Typeapplication/json
X-SUQO-Signaturesha256=<hex HMAC-SHA256>
X-SUQO-TimestampUnix timestamp in seconds
User-AgentSUQO-Webhooks/1.0

Example:

POST /hooks/suqo HTTP/1.1
Host: your-app.example.com
Content-Type: application/json
X-SUQO-Signature: sha256=4f2a...c91b
X-SUQO-Timestamp: 1785312724
User-Agent: SUQO-Webhooks/1.0

{"event":"checkout.succeeded","subscription_id":"8f3c...","amount":"1500.00"}

On this page