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
| Event | Sent when |
|---|---|
checkout.succeeded | A checkout payment on your account was finalized successfully |
checkout.failed | A checkout payment failed terminally (no further retry by SUQO) |
subscription.status_changed | A 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.
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"
}| Field | Notes |
|---|---|
subscription_id | UUID of the subscription that moved. |
previous_status / current_status | Both drawn from the subscription status set — see Subscription statuses. |
changed_at | ISO 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, active → due when a renewal comes round, → cancelled when it ends. Branch on the pair, not on the event name alone: inactive → active is a reactivation, pending_checkout → active 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
amountand no payment outcome in the body. A subscription reachingactivedoes not by itself mean money arrived —checkout.succeededis 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_atto decide which is newer — see Retries.
Request format
Every delivery is a POST with:
| Header | Value |
|---|---|
Content-Type | application/json |
X-SUQO-Signature | sha256=<hex HMAC-SHA256> |
X-SUQO-Timestamp | Unix timestamp in seconds |
User-Agent | SUQO-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"}