Polling without webhooks
A cron job and one call: GET /v1/changes tells you every invoice, deposit and payout that changed since you last asked. Every plan has it.
Webhooks push events to your server the moment they happen and are included from the Growth plan up. On Starter, or anywhere you would rather not run a public endpoint, poll instead. You lose nothing but latency: the same event log that feeds webhooks feeds this.
One call per tick
GET /v1/changes?after=918273
Authorization: Bearer mplive_…{
"object": "changes",
"livemode": true,
"invoices": [
{
"id": "inv_01K3…",
"last_event": "invoice.paid",
"sequence": 918301,
"updated": "2026-09-23T10:41:12.004Z",
"data": { /* the invoice, exactly as GET /v1/invoices/inv_01K3… returns it */ }
}
],
"deposits": [ /* same shape */ ],
"payouts": [ /* same shape */ ],
"customers": [],
"wallets": [],
"other": [ /* raw events with no resource shape: balance transfers, conversions… */ ],
"events_scanned": 12,
"has_more": false,
"next_after": 918301
}Store next_after. Send it back as after next time. That is the whole
protocol.
The rules that make it safe
- The cursor is the event
sequence— a per-project counter that only goes up. Two ticks can never see the same change twice or skip one, however long you were down. Gaps in the numbers are normal. - Each resource appears once, in its latest state. An invoice created,
quoted and paid between two ticks comes back as one paid invoice;
last_eventsays which event that was. You never have to replay a sequence of events to work out where something ended up. datais the REST shape. The same deserializer you wrote forGET /v1/invoices/{id}reads it.has_more: truemeans the page filled before the log ran out. Call again immediately with the newnext_after; do not wait for the next cron tick. A call scans 200 events by default and at most 500 (limit).- Filter by type if you only care about money in:
?after=…&type=invoice.paid&type=deposit.confirmed.
The first call
Send nothing to read the whole log from the beginning, or
?since=2026-09-23T00:00:00Z to start from an instant. Either way the
response's next_after is what you store; from then on you only ever send
after.
Which key
Any key with the webhooks:read scope — it is the log-reading scope and is
granted by default. Live keys read live changes, test keys read test changes.
When you move to webhooks
Nothing to re-learn: a webhook envelope carries the same sequence, so the
cursor you stored is where you reconcile from. Keep the poller running for a
day as a check that every event arrived, then switch it off.