Skip to Content
ObjectsOverview

Objects

A webhook’s data and the REST body for the same thing are the same object.

They are built by the same code, so a field map written against one works against the other. A consumer that receives order.paid and later decides to fetch the order to check something sees identical field names in both. Two shapes would be two sets of documentation, two mental models, and a support question every week about why total is an object here and a number there.

These pages are that vocabulary, written down once.

The objects

ObjectReachable byCarried by
Moneyevery amount, everywhere
ShopGET /shop
OrderGET /orders, GET /orders/{id}order.*, booking.confirmed
ProductGET /products, GET /products/{id}
ContactGET /contacts, GET /contacts/{id}contact.created
Subscriptionno endpointsubscription.*
Disputeno endpointdispute.*

The last two are the reason this section exists as a section. Nine of the 16 webhook events carry an object that appears nowhere in the REST API — there is no GET /api/v1/subscriptions/{id} and no dispute endpoint — so unlike an order there is nowhere else to go and read the shape off.

object

Every one of them carries a literal object field:

{ "id": "…", "object": "order", }

so a single handler that receives a webhook can branch on the kind of thing it was given without inspecting the event name, and a value stored in a queue can be recognised later without the context that produced it.

What is never on any of them

Consistent across all seven, and worth stating in one place rather than seven:

  • No Stripe identifiers. stripeSubscriptionId, stripeCustomerId, stripeAccountId, charge ids, payment intent ids. Every one of them names an object in the seller’s Stripe account, and shipping them would let anything holding a payload address that account directly. A Sailo object’s own id is the handle Sailo speaks.
  • No credentials or one-time tokens. No download tokens, no invoice tokens, no ticket tokens, no payment proof URLs. Each of those is a bearer credential in link form.
  • Nothing the seller wrote privately. A contact’s notes column is the seller’s scratchpad about a customer; a dispute’s evidenceSnapshot is the bundle assembled to answer a chargeback. Both exist for one reader and it is not an integration.
  • Nothing about another shop. Every object comes from one shop and names nothing outside it.

Field types

The type column on each page is written for a reader, not as TypeScript:

WrittenMeans
stringA JSON string. Never null where the type does not say so.
string | nullPresent on every payload; null when there is no value.
numberAn integer unless the field says otherwise.
moneyA money object, never a bare number.
object | nullThe whole object is null when absent — see nulls.
object[]An array, possibly empty. Never null.
"order"Exactly that literal string.

Timestamps are string | null and always ISO 8601 in UTC.

Last updated on