Event catalog

Everything we can send you.

These are the exact events spuke 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: Spuke-Signature, Spuke-Event-Id, Spuke-Event-Type, Spuke-Environment, Spuke-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.payment_failed A subscription invoice could not be collected; dunning continues Warn the customer — don't cut access on the first failure.
customer.subscription.deleted A subscription ended Revoke entitlements.

There are currently no invoice.created / invoice.paid / subscription.created app events. Poll /v1/invoices and /v1/subscriptions if you need that state.


Not available yet

Standalone refund.*, dispute.*, payout.* and customer.* events are not delivered to apps. Read that data through the App API instead:

You want Use
Refund state checkout.session.refunded event + GET /v1/refunds
Dispute state GET /v1/disputes (no dispute events are pushed to apps)
Payouts GET /v1/payouts (restricted scope)
Customers fields on the payment/invoice objects

Minimal handler

export async function POST(req) {
  const raw = await req.text();
  if (!verifySignature(raw, req.headers.get("spuke-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.