Documentation
Webhooks

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 TypePayload RootMeaning
subscription.createdsubscriptionA new subscription has been created in Flo.
subscription.updatedsubscriptionA subscription changed without being cancelled, renewed, paused, resumed, or reactivated.
subscription.cancelledsubscriptionA subscription has been scheduled for cancellation or cancelled in Flo.
subscription.renewedsubscriptionA recurring subscription period was successfully billed.
subscription.reactivatedsubscriptionA previously cancelled subscription was restored.
subscription.pausedsubscriptionA subscription has been paused.
subscription.resumedsubscriptionA paused subscription has resumed.
subscription.expiredsubscriptionA subscription has ended and is no longer active.
item.purchaseditemA one-off item or invoice line was successfully purchased.
item.refundeditemA previously purchased item was refunded.
item.charged_backitemMoney was withdrawn after a provider dispute.
invoice.createdinvoiceA new Flo invoice has been created.
invoice.updatedinvoiceA Flo invoice changed without being deleted, paid, or becoming overdue.
invoice.deletedinvoiceA Flo invoice was deleted.
invoice.payment_failedinvoiceA Flo invoice charge failed without producing a paid outcome.
invoice.paidinvoiceA Flo invoice was paid successfully.
invoice.overdueinvoiceA Flo invoice is overdue.
payment.authorizedpaymentA card hold is active and awaiting capture. Funds are not paid.
payment.capture_failedpaymentA later capture attempt failed without producing a paid outcome.
payment.authorization_voidedpaymentA merchant released an uncaptured hold.
payment.authorization_expiredpaymentThe provider expired an uncaptured hold.
dispute.createddisputeA provider dispute entered the Flo dispute lifecycle. Any money movement still arrives separately as item.charged_back and invoice.updated.
dispute.updateddisputeA 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.wondisputeThe dispute closed in your favor. This outcome is terminal.
dispute.lostdisputeThe dispute closed in the cardholder's favor. This outcome is terminal.
pre_dispute_alert.createdpreDisputeAlertAn early warning was raised before any dispute exists. It can arrive before Flo links the alert to a customer or payment.
pre_dispute_alert.updatedpreDisputeAlertAn 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:

EventExpected state
payment.authorizedpayment.status is authorized; retain payment.id and act before authorizationExpiresAt.
payment.capture_failedNo paid state was produced. Re-read the payment before retrying the same logical capture.
payment.authorization_voidedpayment.status is voided and authorizationVoidReason is merchant_requested.
payment.authorization_expiredpayment.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:

EventExpected state
dispute.createdThe 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.updatedA 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.wondispute.status is won. The outcome is final for this dispute.id.
dispute.lostdispute.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:

statusMeaning
openThe dispute is active. When actionRequired is true, the provider is waiting for your evidence or refund before responseDueAt.
under_reviewYour response was submitted and the provider or card network is deciding. No action is required.
wonThe dispute closed in your favor. Terminal.
lostThe dispute closed in the cardholder's favor. Terminal.

phase records which round of the dispute is running:

phaseMeaning
initialThe first round, from the opening of the dispute to its first decision.
appealA further round after a decision, for example pre-arbitration or arbitration.

Apply these rules in your handler:

  • dispute.won and dispute.lost are terminal. Once a dispute.id has reached won or lost, Flo never sends a later open, 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 a dispute.updated that improves paymentIds or linkStatus while status stays the same.
  • A dispute with phase set to appeal is not terminal. An appealable decision arrives as dispute.updated with phase: "appeal" and a non-terminal status, and dispute.won or dispute.lost follows only when the appeal settles.
  • Act on actionRequired and responseDueAt, never on a provider's own status names. responseDueAt is set only while action is required and is null otherwise.
  • Ambiguous provider outcomes are retained for review rather than reported as won or lost. 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 eventId and sequence, 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:

EventExpected state
pre_dispute_alert.createdThe first event for a preDisputeAlert.id. Start the response clock from responseDueAt when actionRequired is true.
pre_dispute_alert.updatedA 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:

statusMeaningactionRequired
needs_responseThe alert source expects a merchant response, typically a refund or evidence, before responseDueAt.true
under_reviewA response was received and the alert source is reviewing it.false
closedThe alert closed without further action. Terminal.false
preventedThe charge was resolved before it became a dispute, for example through a refund. Terminal.false
unknownFlo 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.

On this page