Events
Flo webhook event types and example payloads.
Events
Checkout metadata is available on the primary object of the payment, item, subscription, and invoice families. Read preserve checkout context with metadata for the lifecycle, then see checkout metadata payload placement for payment, item, subscription, and invoice examples plus an idempotent correlation handler.
Flo currently emits six normalized resource families: subscription, item, invoice, payment, dispute, and pre_dispute_alert (payload root preDisputeAlert).
Examples below are illustrative. Optional fields may be null or omitted depending on the billing activity that produced the event.
The top-level eventId shown in each example is Flo's delivery identifier for that webhook delivery, not the upstream gateway event id.
On invoice events, Flo may also include root-level subscription and/or item when it can resolve linked checkout records from the local transaction.
The outbound event shape is the same regardless of which gateway produced the underlying activity. For the inbound PayPal event catalog and its mapping to the outbound events on this page, see PayPal webhook events.
Event Catalog
| Event Type | Payload Root | Meaning |
|---|---|---|
subscription.created | subscription | A new subscription has been created in Flo. |
subscription.updated | subscription | A subscription changed without being cancelled, renewed, paused, resumed, or reactivated. |
subscription.cancelled | subscription | A subscription has been scheduled for cancellation or cancelled in Flo. |
subscription.renewed | subscription | A recurring subscription period was successfully billed. |
subscription.reactivated | subscription | A previously cancelled subscription was restored. |
subscription.paused | subscription | A subscription has been paused. |
subscription.resumed | subscription | A paused subscription has resumed. |
subscription.expired | subscription | A subscription has ended and is no longer active. |
item.purchased | item | A one-off item or invoice line was successfully purchased. |
item.refunded | item | A previously purchased item was refunded. |
item.charged_back | item | Money was withdrawn after a provider dispute. |
invoice.created | invoice | A new Flo invoice has been created. |
invoice.updated | invoice | A Flo invoice changed without being deleted, paid, or becoming overdue. |
invoice.deleted | invoice | A Flo invoice was deleted. |
invoice.payment_failed | invoice | A Flo invoice charge failed without producing a paid outcome. |
invoice.paid | invoice | A Flo invoice was paid successfully. |
invoice.overdue | invoice | A Flo invoice is overdue. |
payment.authorized | payment | A card hold is active and awaiting capture. Funds are not paid. |
payment.capture_failed | payment | A later capture attempt failed without producing a paid outcome. |
payment.authorization_voided | payment | A merchant released an uncaptured hold. |
payment.authorization_expired | payment | The provider expired an uncaptured hold. |
dispute.created | dispute | A provider dispute entered the Flo dispute lifecycle. Any money movement still arrives separately as item.charged_back and invoice.updated. |
dispute.updated | dispute | A material change that is not itself an outcome transition: the dispute's status, phase, deadline, reason, amount, or payment links moved. Appeals arrive here, and so does a later payment link on a dispute that already reached won or lost. |
dispute.won | dispute | The dispute closed in your favor. This outcome is terminal. |
dispute.lost | dispute | The dispute closed in the cardholder's favor. This outcome is terminal. |
pre_dispute_alert.created | preDisputeAlert | An early warning was raised before any dispute exists. It can arrive before Flo links the alert to a customer or payment. |
pre_dispute_alert.updated | preDisputeAlert | An alert changed: its status, action requirement, deadline, amount, or payment links moved, including a later link to a Flo payment. |
Payment authorisation events
The payment events report the hold lifecycle. None is a successful-purchase signal. For an item-only manual-capture checkout, item.purchased remains the canonical fulfilment event and is emitted only after the full capture succeeds. There is no payment.captured event.
All four events use this provider-neutral payload shape:
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41b01",
"eventVersion": "v1",
"objectType": "payment",
"eventType": "payment.authorized",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "customer_123",
"email": "customer@example.com"
},
"payment": {
"id": "3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0",
"checkoutSessionId": "fd4475e4-dc19-4439-b830-c9c8f67a35e7",
"amount": 49.99,
"currency": "GBP",
"status": "authorized",
"authorizationExpiresAt": "2026-08-11T14:30:00.000Z",
"authorizationVoidReason": null
}
}Interpret the event-specific fields as follows:
| Event | Expected state |
|---|---|
payment.authorized | payment.status is authorized; retain payment.id and act before authorizationExpiresAt. |
payment.capture_failed | No paid state was produced. Re-read the payment before retrying the same logical capture. |
payment.authorization_voided | payment.status is voided and authorizationVoidReason is merchant_requested. |
payment.authorization_expired | payment.status is voided and authorizationVoidReason is expired. |
Subscription Events
Flo-managed subscription recovery uses the existing subscription and invoice event types. A failed round does not emit subscription.renewed, and the first failure is not terminal. See Recover failed rebills automatically for the complete ordered sequences through recovery and exhaustion.
subscription.created
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a01",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.created",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "active",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"startDate": "2026-04-11T00:00:00.000Z",
"endDate": "2026-05-09T00:00:00.000Z"
}
}subscription.updated
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a02",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.updated",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership-plus",
"name": "Hub Membership Plus",
"description": "Hub membership plus billed every four weeks",
"state": "active",
"quantity": 2,
"currency": "GBP",
"amount": 22,
"total": 44,
"startDate": "2026-04-11T00:00:00.000Z",
"endDate": "2026-05-09T00:00:00.000Z"
}
}subscription.cancelled
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a03",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.cancelled",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "canceled",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"cancelAt": "2026-05-09T00:00:00.000Z",
"canceledAt": "2026-04-11T10:15:00.000Z"
}
}subscription.renewed
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a04",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.renewed",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "active",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"startDate": "2026-04-11T00:00:00.000Z",
"endDate": "2026-05-09T00:00:00.000Z",
"invoiceDetails": {
"gatewayEventType": "invoice.paid",
"invoiceId": "in_123",
"invoiceStatus": "paid",
"transactionId": "txn_123",
"paidAt": "2026-04-11T10:20:00.000Z",
"currency": "GBP",
"amountPaid": 22,
"total": 22
}
}
}subscription.reactivated
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a05",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.reactivated",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "active",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"cancelAt": null,
"canceledAt": null
}
}subscription.paused
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a06",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.paused",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "paused",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"pausedAt": "2026-04-11T10:30:00.000Z",
"resumeAt": "2026-05-11T10:30:00.000Z"
}
}subscription.resumed
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a07",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.resumed",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "active",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"pausedAt": null,
"resumeAt": null
}
}subscription.expired
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a08",
"eventVersion": "v1",
"objectType": "subscription",
"eventType": "subscription.expired",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"state": "expired",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"endDate": "2026-05-09T00:00:00.000Z"
}
}These events are produced from subscription lifecycle changes and recurring billing activity.
Item Events
Required migration for backend v1.7.1. Before enabling the release, add item.charged_back to every applicable webhook endpoint subscription and treat its amount as money withdrawn. Move any branch that detects a PayPal chargeback from item.refunded to this new event. Existing payload field shapes are unchanged.
item.purchased
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a09",
"eventVersion": "v1",
"objectType": "item",
"eventType": "item.purchased",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"item": {
"uuid": "da634f98-8748-4e90-bb5d-f3df74003b57",
"id": "priority-support",
"name": "Priority Support",
"description": "Priority Support",
"quantity": 1,
"currency": "GBP",
"amount": 5,
"total": 5,
"invoiceState": "paid",
"invoiceDetails": {
"gatewayEventType": "invoice.paid",
"invoiceId": "in_123",
"invoiceStatus": "paid",
"transactionId": "txn_123",
"paidAt": "2026-04-11T10:20:00.000Z",
"currency": "GBP",
"amountPaid": 27,
"total": 27
}
},
"payment": {
"id": "01282c3a-6a31-4d69-b2f0-b739f126d3b1",
"checkoutSessionId": "cb920260-c80d-4a05-895b-e3bb36a51752",
"amount": 27,
"currency": "GBP",
"status": "succeeded",
"authorizationExpiresAt": null,
"authorizationVoidReason": null
}
}payment is the Flo payment behind the purchase, at the event root beside item. In this example, Priority Support is one GBP 5 line of a GBP 27 invoice: item.amount and item.total describe the line, while payment.amount and payment.currency describe the whole GBP 27 payment. payment.id is the Flo payment UUID, not a provider identifier such as invoiceDetails.transactionId. See Item Purchase Payment for every field and for when payment is absent.
item.refunded
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0a",
"eventVersion": "v1",
"objectType": "item",
"eventType": "item.refunded",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"item": {
"uuid": "da634f98-8748-4e90-bb5d-f3df74003b57",
"id": "priority-support",
"name": "Priority Support",
"description": "Priority Support",
"quantity": 1,
"currency": "GBP",
"amount": 5,
"total": 5,
"invoiceState": "voided",
"invoiceDetails": {
"gatewayEventType": "refund.created",
"invoiceId": "in_123",
"invoiceStatus": "paid",
"transactionId": "re_123",
"transactionType": "refund",
"chargeId": "ch_123",
"currency": "GBP",
"amountPaid": 0,
"total": 5
}
}
}item.refunded is reserved for genuine merchant or administrative refunds. Chargebacks do not emit item.refunded.
item.charged_back
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a10",
"eventVersion": "v1",
"objectType": "item",
"eventType": "item.charged_back",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"item": {
"uuid": "da634f98-8748-4e90-bb5d-f3df74003b57",
"id": "priority-support",
"name": "Priority Support",
"description": "Priority Support",
"quantity": 1,
"currency": "GBP",
"amount": 5,
"total": 5,
"invoiceState": "voided",
"invoiceDetails": {
"gatewayEventType": "charge.dispute.funds_withdrawn",
"invoiceId": "in_123",
"invoiceStatus": "under_review",
"transactionId": "dp_123",
"transactionType": "chargeback",
"chargeId": "ch_123",
"currency": "GBP",
"amountPaid": 0,
"total": 5
}
}
}item.charged_back means a provider dispute caused money to be withdrawn. It replaces item.refunded for chargebacks; genuine refunds continue to emit item.refunded. A chargeback withdrawal also emits invoice.updated for the changed invoice state.
Reinstatements, including a Stripe win or funds reinstatement and PayPal chargeback_reimbursement, restore the invoice through invoice.updated only. They emit no second item.charged_back and no item.refunded.
This contract applies prospectively. It does not reclassify or replay historical transaction rows.
Together, the item events represent purchases, genuine refunds, and provider-dispute withdrawals as distinct financial events.
Invoice Events
invoice.created
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0b",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.created",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"quantity": 1,
"currency": "GBP",
"amount": 24,
"total": 24,
"metadata": {
"source": "checkout-subscription"
}
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"subtotal": 22,
"total": 22,
"paidAt": null,
"hostedInvoiceUrl": "https://example.com/invoice",
"invoicePdfUrl": "https://example.com/invoice.pdf",
"invoiceDetails": {
"gatewayEventType": "invoice.created",
"invoiceId": "in_123",
"invoiceStatus": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"total": 22
}
}
}invoice.updated
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0c",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.updated",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 24,
"amountPaid": 0,
"subtotal": 24,
"total": 24,
"paidAt": null,
"hostedInvoiceUrl": "https://example.com/invoice",
"invoicePdfUrl": "https://example.com/invoice.pdf",
"invoiceDetails": {
"gatewayEventType": "invoice.updated",
"invoiceId": "in_123",
"invoiceStatus": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 24,
"amountPaid": 0,
"total": 24
}
}
}invoice.deleted
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0d",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.deleted",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"metadata": {
"source": "checkout-subscription"
}
},
"item": {
"uuid": "da634f98-8748-4e90-bb5d-f3df74003b57",
"id": "priority-support",
"name": "Priority Support",
"description": "Priority support add-on",
"quantity": 1,
"currency": "GBP",
"amount": 5,
"total": 5,
"metadata": {
"source": "checkout-item"
}
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"subtotal": 22,
"total": 22,
"paidAt": null,
"hostedInvoiceUrl": "https://example.com/invoice",
"invoicePdfUrl": "https://example.com/invoice.pdf",
"invoiceDetails": {
"gatewayEventType": "invoice.deleted",
"invoiceId": "in_123",
"invoiceStatus": "open",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"total": 22
}
}
}invoice.payment_failed
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a10",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.payment_failed",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"subscription": {
"uuid": "d8a2ad28-b98f-4cb6-bf46-f11cc0f5df16",
"id": "hub-membership",
"name": "Hub Membership",
"description": "Hub membership billed every four weeks",
"quantity": 1,
"currency": "GBP",
"amount": 22,
"total": 22,
"metadata": null
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "pastDue",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"subtotal": 22,
"total": 22,
"paidAt": null,
"hostedInvoiceUrl": null,
"invoicePdfUrl": null,
"invoiceDetails": null
}
}During Flo-managed recovery, invoice.payment_failed is not terminal. Each definitive failed round can emit one event with its own eventId. The first retryable failure is followed by subscription.updated with subscription.state: "past_due"; later failed rounds omit that duplicate state transition. Wait for subscription.renewed after confirmed payment or subscription.expired after terminal exhaustion.
invoice.paid
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0e",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.paid",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "paid",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 27,
"amountPaid": 27,
"subtotal": 27,
"total": 27,
"paidAt": "2026-04-11T10:20:00.000Z",
"hostedInvoiceUrl": "https://example.com/invoice",
"invoicePdfUrl": "https://example.com/invoice.pdf",
"invoiceDetails": {
"gatewayEventType": "invoice.paid",
"invoiceId": "in_123",
"invoiceStatus": "paid",
"billingReason": "subscription_cycle",
"transactionId": "pi_123",
"currency": "GBP",
"amountDue": 27,
"amountPaid": 27,
"total": 27
}
}
}invoice.overdue
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41a0f",
"eventVersion": "v1",
"objectType": "invoice",
"eventType": "invoice.overdue",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "user_123",
"email": "user@example.com"
},
"invoice": {
"id": "7bb5ce14-59e0-41ee-8fb7-84d6967cdb68",
"invoiceNumber": "FLO-1001",
"state": "pastDue",
"billingReason": "subscription_cycle",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"subtotal": 22,
"total": 22,
"paidAt": null,
"hostedInvoiceUrl": "https://example.com/invoice",
"invoicePdfUrl": "https://example.com/invoice.pdf",
"invoiceDetails": {
"gatewayEventType": "invoice.overdue",
"invoiceId": "in_123",
"invoiceStatus": "open",
"billingReason": "subscription_cycle",
"transactionId": "pi_123",
"currency": "GBP",
"amountDue": 22,
"amountPaid": 0,
"total": 22
}
}
}This event indicates that Flo still considers the invoice collectible, but it is now overdue.
Dispute events
Dispute events report the lifecycle of a provider dispute in Flo terms, with one stable dispute.id from creation to a final outcome. They describe the case only. Money movement caused by a dispute still arrives through item.charged_back and invoice.updated, so item.charged_back is unaffected by this family: it remains the financial-movement event and is not an alias of any dispute.* event.
The payload is the same whichever gateway raised the dispute. Provider statuses, reason codes, and identifiers are normalized into the Flo values described in Dispute Payload and never appear in the payload, so do not infer provider-specific fields or statuses from it.
Every dispute event uses the dispute payload root:
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41c01",
"eventVersion": "v1",
"objectType": "dispute",
"eventType": "dispute.created",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "customer_123"
},
"dispute": {
"id": "6f1c2a0e-5b7d-4e3a-9c48-2d1f0b7e9a11",
"status": "open",
"phase": "initial",
"actionRequired": true,
"responseDueAt": "2026-09-18T23:59:59.000Z",
"amount": 49.99,
"currency": "USD",
"reason": "fraud_or_unauthorized",
"paymentIds": ["3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0"],
"linkStatus": "linked",
"sequence": 841832100,
"createdAt": "2026-09-04T10:15:00.000Z",
"updatedAt": "2026-09-04T10:15:00.000Z"
}
}The user object carries only id and clientUserId. Flo includes it when the dispute resolves to exactly one Flo user and omits it otherwise, so treat it as optional. paymentIds lists the Flo payment ids linked to the dispute and can hold zero, one, or several ids. sequence is an increasing integer scoped to the dispute.id. Compare it against the last value you stored for that dispute.id, but never assume a starting value: it does not start at one, and it is only comparable within one id.
Interpret each event as follows:
| Event | Expected state |
|---|---|
dispute.created | The first event for a dispute.id. dispute.status is open. When Flo first observes a dispute that is already past that state, the matching dispute.updated, dispute.won, or dispute.lost follows with the next sequence. |
dispute.updated | A material change that is not itself an outcome transition: status moved between open and under_review, phase moved to appeal, actionRequired, responseDueAt, amount, currency, or reason changed, or paymentIds and linkStatus improved. It also arrives after won or lost when only paymentIds or linkStatus improves and status stays the same, so keep handling it once a dispute is terminal. Read the whole dispute object rather than diffing individual fields. |
dispute.won | dispute.status is won. The outcome is final for this dispute.id. |
dispute.lost | dispute.status is lost. The outcome is final for this dispute.id. |
Lifecycle and terminal outcomes
status moves through open and under_review before settling on won or lost:
status | Meaning |
|---|---|
open | The dispute is active. When actionRequired is true, the provider is waiting for your evidence or refund before responseDueAt. |
under_review | Your response was submitted and the provider or card network is deciding. No action is required. |
won | The dispute closed in your favor. Terminal. |
lost | The dispute closed in the cardholder's favor. Terminal. |
phase records which round of the dispute is running:
phase | Meaning |
|---|---|
initial | The first round, from the opening of the dispute to its first decision. |
appeal | A further round after a decision, for example pre-arbitration or arbitration. |
Apply these rules in your handler:
dispute.wonanddispute.lostare terminal. Once adispute.idhas reachedwonorlost, Flo never sends a lateropen,under_review, or opposite outcome for it, and repeated or stale provider notifications do not regress the result. The only later event for a terminal dispute is adispute.updatedthat improvespaymentIdsorlinkStatuswhilestatusstays the same.- A dispute with
phaseset toappealis not terminal. An appealable decision arrives asdispute.updatedwithphase: "appeal"and a non-terminalstatus, anddispute.wonordispute.lostfollows only when the appeal settles. - Act on
actionRequiredandresponseDueAt, never on a provider's own status names.responseDueAtis set only while action is required and isnullotherwise. - Ambiguous provider outcomes are retained for review rather than reported as
wonorlost. When a provider closes a dispute without a usable outcome, or reports a state Flo cannot classify, no event is sent; a later clear outcome is reported normally. - Repeated, overlapping, or older provider notifications never create duplicate transitions. Flo either replays the transition it already recorded for that provider event, which reaches you with the same
eventIdandsequence, or drops the notification as non-material.
Pre-Dispute Alert events
A Pre-Dispute Alert is an early warning that a cardholder or issuer is likely to dispute a charge. It moves no money and precedes any dispute, so it gives you a window to refund or respond before a dispute is raised. Flo normalizes every alert source it monitors into this one lifecycle, with a stable preDisputeAlert.id for the whole life of the alert.
The payload names no alert network or gateway and carries no source identifiers, raw source statuses, matching evidence, or card data beyond the last four digits. Do not infer provider-specific fields or statuses from it: build against the status, actionRequired, responseDueAt, and linkStatus values documented here and in Pre-Dispute Alert Payload.
Alerts are tenant-scoped and time-sensitive. Flo delivers an alert to the client that owns it as soon as it can attribute it, which can be before Flo links the alert to any customer or payment. An unlinked alert has an empty paymentIds, a linkStatus of unmatched or action_required, and no user object:
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41d01",
"eventVersion": "v1",
"objectType": "pre_dispute_alert",
"eventType": "pre_dispute_alert.created",
"preDisputeAlert": {
"id": "b2e7c9d4-1f3a-4c6e-8a5b-7d9e0f1a2b3c",
"status": "needs_response",
"actionRequired": true,
"reason": "fraud_or_unauthorized",
"responseDueAt": "2026-09-06T09:58:12.000Z",
"amount": 49.99,
"currency": "USD",
"alertAt": "2026-09-04T09:58:12.000Z",
"cardLast4": "4242",
"merchantDescriptor": "EXAMPLE*STORE",
"acquirerReferenceNumber": "24692166247000000000123",
"authorizationCode": "A1B2C3",
"paymentIds": [],
"linkStatus": "unmatched",
"sequence": 841831092,
"createdAt": "2026-09-04T09:58:12.000Z",
"updatedAt": "2026-09-04T09:58:12.000Z"
}
}Every descriptive field (reason, responseDueAt, amount, currency, alertAt, cardLast4, merchantDescriptor, acquirerReferenceNumber, and authorizationCode) is nullable, because an alert source may omit any of them. A later notification that omits a field never clears a value an earlier one established.
When Flo later verifies a link to a Flo payment, the alert keeps the same id, sequence rises, and pre_dispute_alert.updated reports the link. Flo also includes user from that point when the linked payments resolve to exactly one Flo user:
{
"eventId": "9f6f8b54-8e2d-4f15-8c8a-d7b6d9f41d02",
"eventVersion": "v1",
"objectType": "pre_dispute_alert",
"eventType": "pre_dispute_alert.updated",
"user": {
"id": "3b4d9a11-0ce8-4a88-9cb1-b4f43d03d2b7",
"clientUserId": "customer_123"
},
"preDisputeAlert": {
"id": "b2e7c9d4-1f3a-4c6e-8a5b-7d9e0f1a2b3c",
"status": "needs_response",
"actionRequired": true,
"reason": "fraud_or_unauthorized",
"responseDueAt": "2026-09-06T09:58:12.000Z",
"amount": 49.99,
"currency": "USD",
"alertAt": "2026-09-04T09:58:12.000Z",
"cardLast4": "4242",
"merchantDescriptor": "EXAMPLE*STORE",
"acquirerReferenceNumber": "24692166247000000000123",
"authorizationCode": "A1B2C3",
"paymentIds": ["3c54b6ac-7ad5-4e56-9c2c-5a80c2ef40d0"],
"linkStatus": "linked",
"sequence": 841831093,
"createdAt": "2026-09-04T09:58:12.000Z",
"updatedAt": "2026-09-04T10:02:40.000Z"
}
}Key the alert on preDisputeAlert.id and update the record you already hold rather than creating a second one. See Correlate an unlinked Pre-Dispute Alert for a worked handler.
Interpret each event as follows:
| Event | Expected state |
|---|---|
pre_dispute_alert.created | The first event for a preDisputeAlert.id. Start the response clock from responseDueAt when actionRequired is true. |
pre_dispute_alert.updated | A material change: status, actionRequired, reason, responseDueAt, amount, currency, paymentIds, or linkStatus moved. A change to the descriptive hints alone does not produce an event. Read the whole preDisputeAlert object. |
status and actionRequired describe what the alert needs from you:
status | Meaning | actionRequired |
|---|---|---|
needs_response | The alert source expects a merchant response, typically a refund or evidence, before responseDueAt. | true |
under_review | A response was received and the alert source is reviewing it. | false |
closed | The alert closed without further action. Terminal. | false |
prevented | The charge was resolved before it became a dispute, for example through a refund. Terminal. | false |
unknown | Flo could not classify the source state. Treat it as needing attention. | true |
closed and prevented are terminal: Flo never regresses an alert past either state, and repeated, overlapping, or stale notifications produce no duplicate events. An alert that never links to a Flo payment or customer is still delivered with an empty paymentIds; if a later observation links it, the same id and the next sequence are used.