Event catalog

Everything we can send you.

These are the exact events Southbill delivers to app webhook endpoints today. Anything not listed here is never sent — subscribe only to what exists.

How to think about events

Concept Explanation
Event = past-tense fact payment_intent.succeeded means it already happened. You react, you never approve.
One action can fire several events A completed checkout fires payment_intent.succeeded and checkout.session.completed. Use the checkout event as your fulfilment trigger.
At-least-once delivery The same event can arrive twice. Deduplicate on the event id.
No ordering guarantee Events can arrive out of order. If order matters, re-fetch the object from the API.
Unknown types are safe to ignore We add events without a breaking change. Never crash on an unfamiliar type.
Subscription is per endpoint Unsubscribed events are not delivered and not stored. * and checkout.* wildcards are supported.

The envelope

Every delivery has the same outer shape; only type and data.object differ.

{
  "id": "evt_9f1c2ab34d5e6f70",
  "object": "event",
  "type": "checkout.session.completed",
  "environment": "live",
  "installation": "3f0c…-uuid",
  "created": 1755423672,
  "data": { "object": { "…": "resource payload" } }
}
Field What it is Why it matters
id Unique event ID, prefix evt_ Your idempotency key. Store it and skip duplicates.
object Always "event" Lets you store raw payloads generically.
type What happened The only field you should branch on.
environment test or live Never let a sandbox event touch production data.
installation The installation this event belongs to Routes the event to the right tenant in your app.
created Unix timestamp in seconds (not RFC 3339) Use it to resolve out-of-order arrivals.
data.object The resource payload at the time of the event Shape depends on type — see Event payloads.

Delivery headers: Southbill-Signature, Southbill-Event-Id, Southbill-Event-Type, Southbill-Environment, Southbill-Delivery-Attempt.


Checkout

The checkout session is the object your app should treat as the order.

Event What it means What you should do
checkout.session.completed The shopper paid and the session flipped to complete Fulfil the order here. Fired exactly once per session.
checkout.session.expired The session timed out unpaid Release reserved stock, optionally send a recovery mail.
checkout.session.canceled The shopper or the merchant cancelled before payment Close the order; cancellation_reason may be set.
checkout.session.async_payment_pending A delayed method (e.g. bank debit) is processing Show "payment pending"; do not ship yet.
checkout.session.async_payment_failed The delayed payment failed failure_code / failure_message explain why.
checkout.session.refunded The related charge was refunded amount_refunded and fully_refunded tell you how much.

Payments (payment intents)

Lower-level than checkout — use these only if you need the payment view.

Event What it means What you should do
payment_intent.succeeded Funds captured Mark paid. Prefer checkout.session.completed for order fulfilment.
payment_intent.payment_failed Declined; failure_code, decline_code, failure_message explain it Offer a retry; never retry silently.
payment_intent.requires_action Waiting on the shopper (3-D Secure, app confirmation) Nothing to do server-side; the shopper continues in checkout.
payment_intent.canceled The payment was cancelled or abandoned Free reserved inventory.

Billing

Event What it means What you should do
invoice.created An invoice was created Mirror it; nothing is collected yet.
invoice.sent The invoice was sent to the customer Track dunning state in your app.
invoice.finalized The invoice is locked and collectible Safe to render as an official document.
invoice.paid / invoice.payment_succeeded The invoice was collected Grant entitlements.
invoice.payment_failed Collection failed; dunning continues Warn the customer — don't cut access on the first failure.
invoice.payment_action_required The customer must authenticate (3-D Secure) Ask the customer to finish the payment.
invoice.upcoming A renewal invoice is coming up Hook for pre-billing notices.
invoice.marked_uncollectible / invoice.voided Written off or voided Stop dunning.
subscription.created / .updated Subscription started or changed Sync plan, quantity, period end.
customer.subscription.trial_will_end Trial ends soon Nudge for a payment method.
customer.subscription.paused / .resumed Billing paused or resumed Suspend/restore entitlements.
customer.subscription.deleted A subscription ended Revoke entitlements.
customer.created / .updated / .deleted Customer record changed Keep your mirror in sync.

Refunds, disputes and payouts

These arrive as standalone events — no polling required.

Event What it means
charge.refunded The refunded total of a charge changed (full or partial).
refund.created / .updated / .failed Lifecycle of a single refund.
charge.dispute.created A chargeback was opened; evidence_due_by is the deadline.
charge.dispute.updated Status or evidence state changed.
charge.dispute.closed Final outcome (won / lost).
charge.dispute.funds_withdrawn / .funds_reinstated Disputed funds debited or returned.
checkout.session.dispute.created / .closed The same chargeback, expressed on the checkout session.
payout.created / .updated / .paid Payout lifecycle to the merchant's bank account.
payout.failed / .canceled The payout did not arrive — see failure_code.
payout.reconciliation_completed The payout is fully reconciled and safe to book.

Payout events require the restricted payouts:read scope.


Southbill Store (orders & catalogue)

Store events are separate from checkout.session.* on purpose: checkout.session.completed means "money arrived", order.paid means "a Southbill Store order exists and must be fulfilled". Invoice money never appears here — it uses invoice.*.

Event What it means What you should do
order.created A store order row was created (also via the Store API) Mirror the order; it may still be unpaid.
order.paid The store order was paid Fulfil here. product, variant, order_number are set.
order.updated Status or tracking changed (e.g. shipped) Sync status, tracking_carrier / tracking_number / tracking_url.
order.refunded The order was refunded amount_refunded, fully_refunded.
order.canceled The order was cancelled Release stock, close your record.
product.created / product.updated Catalogue changed (dashboard or Store API) Re-fetch the product incl. variants and prices.

The order payload is object: "store.order" with source: "store"; id and payment both hold the checkout session ID. Requires the store:read scope.


Lifecycle

Event What it means
app.installed A merchant installed your app (first OAuth grant).
app.reinstalled An existing installation was re-authorized.
app.uninstalled The merchant removed your app — tokens are already revoked. Delete their data within 30 days.

Minimal handler

export async function POST(req) {
  const raw = await req.text();
  if (!verifySignature(raw, req.headers.get("southbill-signature"), SECRET)) {
    return new Response("bad signature", { status: 400 });
  }
  const event = JSON.parse(raw);
  if (await seen(event.id)) return new Response("ok");   // dedupe
  await store(event.id);
  switch (event.type) {
    case "checkout.session.completed": await fulfil(event.data.object); break;
    case "checkout.session.refunded":  await refund(event.data.object); break;
    default: break;                                      // ignore unknown types
  }
  return new Response("ok");                             // 2xx within 10s
}

See Signatures & retries.