# Event payloads

What each event means and what it carries.

Every delivery uses the same envelope; only `type` and `data.object` differ. `created` is a **Unix timestamp in seconds**.

```json
{ "id": "evt_9f1c2ab34d5e6f70", "object": "event", "type": "checkout.session.completed",
  "environment": "live", "installation": "3f0c…-uuid", "created": 1755423672,
  "data": { "object": { … } } }
```

---

## Checkout session payload

All `checkout.session.*` events carry the same base object, plus a few event-specific fields.

```json
{
  "id": "b6b1…-uuid",
  "object": "checkout.session",
  "status": "complete",
  "environment": "live",
  "amount": 2500,
  "currency": "eur",
  "reference": "cs_2f81c0",
  "metadata": { "order_id": "1042" },
  "payment_intent": "pi_3Nk91x…",
  "setup_intent": null,
  "subscription": null,
  "charge": "ch_3Nk91x…"
}
```

| Field | Meaning |
| --- | --- |
| `id` | Southbill checkout session ID (UUID). Your order key. |
| `status` | `open`, `complete`, `expired`, `failed`, `canceled`. |
| `mode` | `live` or `test` — same meaning as the envelope `environment`. |
| `amount` | Total in minor units (`2500` = €25.00). |
| `currency` | Lowercase ISO-4217. |
| `reference` | Short human-readable session reference shown to the shopper. |
| `metadata` | Key/value map set when the session was created — your safest link to your own order. |
| `payment_intent` | Payment reference at the processor, `null` for setup-only sessions. |
| `setup_intent` | Set instead of `payment_intent` when the session only saved a payment method. |
| `subscription` | Subscription ID when the session started a recurring plan. |
| `charge` | Charge reference once money moved; needed to match refunds/disputes. |

**Event-specific extras**

| Event | Extra fields |
| --- | --- |
| `checkout.session.async_payment_pending` | `payment_status: "processing"`, `status: "open"` |
| `checkout.session.async_payment_failed` | `failure_code`, `failure_message` |
| `checkout.session.canceled` | `cancellation_reason` |
| `checkout.session.refunded` | `amount_refunded` (minor units), `fully_refunded` (boolean) |

```json
{ "type": "checkout.session.refunded",
  "data": { "object": { "id": "b6b1…", "object": "checkout.session", "status": "complete",
    "amount": 2500, "currency": "eur", "amount_refunded": 500, "fully_refunded": false } } }
```

---

## Payment intent payloads

`payment_intent.succeeded`

```json
{ "id": "pi_3Nk91x", "amount": 2500, "amount_received": 2500, "currency": "eur",
  "status": "succeeded", "customer": "cus_8f21", "latest_charge": "ch_3Nk91x",
  "payment_method_types": ["card"], "metadata": { "order_id": "1042" } }
```

| Field | Meaning |
| --- | --- |
| `amount` | Amount requested, minor units. |
| `amount_received` | Amount actually captured — compare both for partial captures. |
| `status` | Processor status, e.g. `succeeded`, `requires_action`, `canceled`. |
| `customer` | Processor customer reference, may be `null` for guest checkout. |
| `latest_charge` | The charge that carries the money; use it to match refunds and disputes. |
| `payment_method_types` | Methods that were allowed on this payment. |
| `metadata` | Whatever the merchant/app set at creation. |

`payment_intent.payment_failed` replaces `amount_received`/`latest_charge` with:

```json
{ "id": "pi_3Nk91x", "amount": 2500, "currency": "eur", "status": "requires_payment_method",
  "customer": null, "failure_code": "card_declined", "decline_code": "insufficient_funds",
  "failure_message": "Your card has insufficient funds.", "metadata": {} }
```

| Field | Meaning |
| --- | --- |
| `failure_code` | Coarse reason (`card_declined`, `expired_card`, …). Branch on this. |
| `decline_code` | Issuer's detailed reason. Show it to the merchant, not the shopper. |
| `failure_message` | Human-readable text, already customer-safe. |

`payment_intent.requires_action` and `payment_intent.canceled` carry the same base fields (`id`, `amount`, `currency`, `status`, `customer`, `metadata`).

---

## Billing payloads

`invoice.payment_failed` and `customer.subscription.deleted` carry the processor invoice/subscription object. Always read `status` before changing entitlements, and treat unknown fields as additive.

---

## Delivery guarantees

At-least-once and unordered. Deduplicate on the event `id`; when order matters, compare `created` or re-fetch the object from the App API.
