Events
Webhooks are how you hear about something as it happens. Events are how you find out what you missed.
GET /eventsEvery event raised for your organisation, newest first — whether or not an endpoint was registered to receive it, and whether or not delivery succeeded.
{
"data": [
{
"id": "260bcc29-a3ee-477b-af28-6b206976aa4d",
"event": "booking.confirmed",
"created_at": "2026-09-22T18:50:54Z",
"data": { "reference": "REQ-6FED6B", "property": "USC Village Apartment" }
}
],
"count": 100,
"next_cursor": "MjAyNi0wOS0yMlQxODo1MDo1NC4yMzc1NjQrMDA6MDA"
}Both kinds of key work here. A carrier key sees its organisation’s events, a provider key sees its own — “what did I miss” is the same question whichever end of the market you are.
Events carry an environment, and you only ever see your own. A live poll
cannot surface a simulated lease.signed, and a sandbox key cannot read real
placements. An event raised by genuine activity on a sandbox request is
filed as sandbox, so your test history is your test history.
Catching up after an outage
Your endpoint was down from 02:00 to 04:00. Rather than reconciling by hand:
GET /events?since=2026-09-22T02:00:00ZThen either act on what comes back directly, or push it through your normal handler by replaying it:
POST /events/{id}/replay{ "data": { "event_id": "260bcc29-…", "queued_to": 1, "replay": true } }202 — it is queued to whichever of your endpoints are subscribed now, so
fix the endpoint first and replay second.
A replayed delivery carries "replay": true in its body and keeps the
original event id, so a handler that has already processed it can recognise
it and do nothing. Make your handler idempotent on id and replays cost you
nothing.
409 no_endpoint means you have no active endpoint subscribed to that event —
there is nowhere to send it. Read it from GET /events/{id} instead.
What we send, and what it looks like
GET /events/typesEvery event your kind of key can receive, each with a representative body:
{
"data": [
{
"event": "payment.succeeded",
"summary": "A charge cleared, with the fee breakdown",
"audience": "carrier",
"simulatable": true,
"sample": {
"id": "00000000-0000-0000-0000-000000000000",
"event": "payment.succeeded",
"created_at": "2026-09-22T19:53:19Z",
"data": {
"reference": "REQ-SANDBOX",
"charge_type": "checkout_direct",
"amount": 4120, "platform_fee": 372, "currency": "usd",
"covers_rent": true, "covers_deposit": false
}
}
}
],
"count": 11
}Build your handler against these rather than reading prose and guessing at field names — matching on a field that does not exist is a mistake that only shows up the first time the event actually fires, which is usually the worst possible moment.
A carrier key sees carrier events; a provider key sees provider events. Listing the other side’s events would just be noise you had to learn to ignore.
simulatable: true means you can make it happen on demand — see below.
Testing your handlers
The events worth handling — a lease signed, a payment cleared, the household declining with a reason — only happen when a real person does something real. Which means the handlers for exactly those events were the ones you could not exercise until production.
POST /events/simulate{ "event": "lease.signed" }{
"data": {
"event_id": "280cae88-…",
"event": "lease.signed",
"queued_to": 1,
"simulated": true,
"note": "Delivered to your sandbox endpoints, signed exactly like a real event."
}
}It is delivered to your sandbox endpoints, signed with the same scheme and
retried the same way. Your handler cannot tell the difference except by looking
— every simulated body carries "simulated": true, in the envelope and in the
data.
Nothing moves. No claim changes, no lease advances, no booking is made and no money is touched. It is a message, not an action.
Send your own body when you want to test a specific shape:
{
"event": "option.declined",
"data": { "reference": "REQ-SANDBOX", "reason": "No parking" }
}Omit data and you get a representative payload for that event, so checking a
handler costs one line rather than an afternoon inventing samples.
Sandbox keys only
A live key gets 403 sandbox_only. A live key must not be able to tell your
production claims system that a lease was signed when it was not.
Simulated events also only ever reach endpoints registered as sandbox, so a
partner running both never has the live system hear about a test.
What you can simulate
request.status_changed, option.declined, option.saved, dates.proposed,
lease.sent, lease.first_signed, lease.signed, payment.succeeded,
payment.failed, booking.confirmed, booking.updated.
Anything else is refused rather than silently accepted. For a plain
connectivity check use POST /webhooks/{id}/test, which sends a
ping.
Filters
| Parameter | Meaning |
|---|---|
event | Comma-separated types, e.g. payment.succeeded,lease.signed |
since | ISO timestamp — everything after it |
limit | Up to 250, default 100 |
cursor | The next_cursor from the previous page |
Cursors are opaque
next_cursor is a base64url string. Pass it back exactly as given and do not
parse it — the format may change.
It used to be the raw timestamp, which looked friendlier and broke in practice:
an ISO timestamp ends in +00:00, a bare + in a query string means a space,
and the cursor came back as an invalid timestamp unless you knew to escape it.
A cursor that only works if you escape it is a trap rather than an API, so it
is now URL-safe by construction.
Retention
Events are kept for 90 days. For anything longer, store what you need on your side as it arrives — these are a recovery mechanism, not an archive.
Events never hold up the thing they describe
If our event log cannot record something, the placement, payment or booking it
describes still happens. Event recording is deliberately unable to fail the
write it is reporting on — an event you never receive is an inconvenience you
can recover from with GET /events; a booking that did not happen because an
analytics row could not be written is not.
The practical consequence for you: treat the event stream as very reliable but not as the system of record. If something matters, the read endpoints are always the truth.
This is not a queue
GET /events does not consume anything. Two systems can both read the same
events, and reading one twice is free. Keep the id of the last event you
processed and pass it as your position.