Skip to main content

Understanding payouts

This guide explains how to go from "the amount I received is not what I expected" to "here is the line that explains it", using the Payouts and Channel Payouts endpoints together.

Both endpoints require the payouts_read scope.

How a booking reaches a payout

A booking reaches a payout by one of two routes. Which one it took decides how many endpoints you need.

Direct booking — SmilyPay collected the guest's card payment. One hop, one endpoint:

traveller ──▶ Smily ──▶ property manager / owner
└── GET /payouts "what Smily paid you, and our fees"

There is no channel and no upstream to check. related_channel_payout_references is empty, and the whole explanation is on the /payouts line. Skip to Direct bookings.

OTA booking — a channel collected and wired Smily a net amount. Two hops, and each endpoint covers exactly one of them.

traveller ──▶ OTA channel ──▶ Smily ──▶ property manager / owner
│ │
│ └── GET /payouts "what Smily paid you, and our fees"
└─────────────── GET /channel_payouts "what the channel wired, and their cuts"

The distinction matters more than it looks:

  • /payouts answers "why is this different from what was collected?" — it accounts for the fees Smily took between collection and payout.
  • /channel_payouts answers "why was that the amount collected?" — the OTA's commission, the city tax it withheld and remits itself, and any deduction it applied.

Smily cannot pay out money it never received. So if a payout looks low and /payouts shows no Smily fee that explains it, the cause is upstream and lives on /channel_payouts. Pivot between them with related_channel_payout_references on each payout item.

An OTA booking can also carry a SmilyPay payment. When the property manager sells an upsell on a channel booking and collects it through SmilyPay, that money will be paid out by Smily independently of the wiretansfer from an OTA for a given booking. Such a booking produces two lines in the same payout — one for each route — and needs both endpoints. See OTA bookings with a SmilyPay upsell.

A worked example

A booking sold through an OTA for 776.68, of which the channel withheld a city tax it remits itself.

Hop 1 — what the channel wired. GET /channel_payouts?booking_id=<id> returns a breakdown line:

{
"booking_id": 4711,
"payout_type": "RESERVATION",
"channel_price_in_cents": 77668,
"ota_commission_in_cents": 11370,
"ota_payment_charge_commission_in_cents": 1061,
"withheld_city_tax_in_cents": 1870,
"net_channel_payout_in_cents": 63367
}

Reading it: the traveller paid 776.68. The channel kept 113.70 commission and 10.61 payment processing, and withheld 18.70 of city tax to remit itself. It wired 633.67.

776.68 − 113.70 − 10.61 − 18.70 = 633.67

Hop 2 — what Smily paid out. The matching /payouts item:

{
"row_type": "channel_payout",
"booking_id": 4711,
"smily_pay_payment_amount_in_cents": 63367,
"smily_traveller_fee_in_cents": 1500,
"smily_payout_amount_in_cents": 61867,
"related_channel_payout_references": ["<channel payout reference>"]
}

Smily received the 633.67, deducted a 15.00 traveller fee, and paid out 618.67.

The two hops chain: 776.68 traveller → 633.67 wired → 618.67 paid out. Every reduction is named on one endpoint or the other.

"My payout is smaller than I expected"

First, check row_type on the /payouts item. A smily_pay_payment line covers money SmilyPay collected, so steps 3 and 4 do not apply to it — go to Direct bookings instead.

Classify by row_type rather than by whether related_channel_payout_references is empty: carry-over and reimbursement lines can be empty without being smily_pay_payment rows, and a smily_pay_payment row can carry references when it covers an upsell on an OTA booking (see below).

Then work through these in order. Each step ends at the field that proves the answer.

1. Is a Smily fee taking it? — /payouts

Find the item for the booking and read the smily_* columns: split payment, traveller fee, cancellation protection, payment gateway. On a channel_payout row those fees are exactly what separates what the channel wired from this line's contribution to the payout:

smily_pay_payment_amount − Σ(smily fees) = balance_in_cents

balance_in_cents is what this line contributes to the payout total, and it can be negative. That is the point of the field: a booking can end up owing Smily money rather than paying property manager or owner. A full refund where SmilyPay fees were already taken is the usual case — nothing is paid out, but the fee still stands, so the line lands negative and is covered by the rest of the payout (or, if there is nothing to cover it here, collected later as a carry-over line).

It is not the same figure as smily_payout_amount_in_cents, which is what was transferred to the property manager and owner. See the Payouts overview for how balance_in_cents is derived per row_type.

On a booking under split payment, both figures cover the property manager's and the owner's shares together, and the line appears in both parties' payouts with the same combined value — so the equation above describes the booking, not your gateway's share. See Direct bookings and split payment.

That equation closes whatever happened, because a fee column always reports what this payout actually funded — but that is not always the fee that was due. See A fee column shows what this payout could fund.

If that closes and the fees are the ones you expected, you have your answer and you are done.

2. Is it a carry-over from an earlier booking? — /payouts

Look for items whose row_type is gateway_fees_debt, traveller_fee_debt, split_payment_fee_debt or cancelation_protection_debt. These pay nothing out and carry a negative balance_in_cents; they reduce the payout total. See Carry-over lines below — and note that the booking they name is the original one the fee was due on, not the booking being paid now.

A fee for a given booking is deferred only when the money Smily received for that booking was not enough to cover it or Smily received no funds for that booking. A fee that can be funded is taken on the booking's own line and never becomes a carry-over.

The mirror of this step is worth knowing too: if a fee on a booking's own line reads lower than you expected, the missing part may have been deferred to a carry-over line in a later payout. Search forward for a carry-over naming the same booking before concluding the fee was wrong.

3. Did the channel deduct against this booking? — /channel_payouts

GET /channel_payouts?booking_id=<id>, then read allocations. Two things to check:

  • Is your booking listed as a source (type: "booking", matching booking_id)? Then the channel clawed money back on this booking, and absorbed_by names the payouts that covered it.
  • Is your booking listed inside someone else's absorbed_by? Then your booking was paid less to cover a different booking's negative movement. original_ota_amount_in_cents shows what your booking would have been paid before the recoup.

In the second case the booking whose payout looks wrong is not the booking that caused the deduction. Check both directions before concluding the figure is incorrect.

4. Was it an account-level movement? — /channel_payouts

An allocation with type: "account" is a deduction the OTA applied across the account rather than to any reservation — an account-wide commission correction, for instance. It has a null booking_id, and it still has to come out of the individual payouts, so it shows up in absorbed_by.

5. Do the channel's own numbers add up?

See When the numbers do not add up.

Direct bookings and split payment

A direct booking is one Smily processed directly — no OTA involved. SmilyPay collected the guest's card payment, so all available details will be on /payouts, on an item with row_type: "smily_pay_payment", payout_type: "Reservation" and an empty related_channel_payout_references.

OTA bookings with a SmilyPay upsell

row_type names how the money was collected, not where the booking came from. smily_pay_payment means "SmilyPay collected this money" — which is true of every direct booking, and also true of an upsell sold for an OTA booking.

When a property manager sells an upsell on a channel booking and collects it through SmilyPay, that payment goes through the same reconciliation a direct booking does. The booking therefore appears twice in the payout:

LineCovers
smily_pay_paymentthe upsell SmilyPay collected
channel_payoutthe net amount the OTA wired

The smily_pay_payment line on such a booking does carry related_channel_payout_references, because the booking really does have channel payouts.

Where the numbers come from

Two sources feed one line, and mixing them up is the usual reason a figure looks off:

  • smily_pay_payment_amount_in_cents, smily_payment_gateway_fee_in_cents and smily_payout_amount_in_cents describe the money that settled in this payout.
  • The property-manager and owner amounts, and the traveller, cancellation-protection and split-payment fees, come from the booking's reconciliation — the record that decides how the collected money is distributed. These are the amounts the reconciliation recorded for the booking, so on a booking that settles across several payouts they do not necessarily describe this payout alone. Reconcile fees across every payout carrying the booking rather than reading one line in isolation.

balance_in_cents is the property-manager amount plus the owner amount. If that is zero, it is instead the negative gateway fee: a booking that paid nothing out but still cost a gateway fee reduces the payout.

A booking can appear in more than one payout

Payments settle individually, so a booking whose money arrives in several payments produces one smily_pay_payment line per payout, each carrying only what settled in it. A line that looks short against the guest's total price is often complete for the payment it covers.

Reconcile across every payout that carries the booking, not against a single one. This can be a reason such a line looks smaller than expected.

When the split is not the configured one

When a booking's money is to be divided between the property manager and the owner, Smily reconciles it before paying out — working out how the collected money splits. That step can fail, usually because the booking is not fully paid, because it carries payments taken outside SmilyPay, or because the booking's source is not eligible for split payments.

The booking still pays out, but on a fallback distribution: the whole amount goes to a single party instead of being divided. Which party is configured per booking — it can be either the property manager or the owner, and one property manager can have different fallbacks for different rentals.

Split payment

With split payment, the SmilyPay payments on a booking are divided between the property manager and the owner instead of going to a single recipient. Two consequences when reading a line:

  • smily_split_payment_fee_in_cents is Smily's fee for performing the split. The reconciliation records how much of it came from each party, so the two sides may absorb unequal shares.
  • The property manager and the owner are paid through their own payment gateways, so their money reaches them in separate payouts. Since /payouts is scoped to one payment_gateway_id, a booking split between the two parties is visible from each gateway separately.
A line under split payment describes the booking, not your share

balance_in_cents and smily_payout_amount_in_cents are the property manager's and owner's shares combined, and the same line appears in both parties' payouts. So Σ(item.balance_in_cents) will not match amount_in_cents on a split-payment account, and you cannot read your own share off the line. Reconcile against amount_in_cents per payout; per-party attributes are planned.

Carry-over lines

When the money Smily received for a booking is too small to cover a Smily fee — a cancelled or heavily refunded reservation, a channel that wired less than the fees due, or a line that nets to nearly nothing — the fee is not waived and not invoiced separately. It stays outstanding and is collected from a later payout, as its own line:

FieldValue
row_typegateway_fees_debt, traveller_fee_debt, split_payment_fee_debt or cancelation_protection_debt
payout_typeSmily carry-over amount collectionthe same string for all four
booking_idthe original booking the fee was due on, not the one being paid now
smily_payout_amount_in_cents0 — this line pays nothing out
balance_in_centsnegative — it reduces the payout total
warning

All four carry-over kinds print the identical payout_type, so it cannot tell you which fee is being collected. Use row_type, or which fee column carries the amount.

Each kind puts its amount in the matching fee column, and leaves every other fee column null:

row_typeColumn carrying the amount
gateway_fees_debtsmily_payment_gateway_fee_in_cents
traveller_fee_debtsmily_traveller_fee_in_cents
split_payment_fee_debtsmily_split_payment_fee_in_cents
cancelation_protection_debtsmily_cancelation_protection_fee_in_cents

The other half of the pair

This is not a second charge. The same mechanism that records the carry-over reduces the fee charged on the original booking's line, so the original was charged less by exactly this amount. If you look at that booking's original payout line you will see the partial — or zero — collection.

That has a consequence worth building around: a fee column can read 0 because the fee was deferred, not because none was due. Nothing on the original line marks it. The carry-over line in the later payout is the only place the difference is stated, which is exactly why these lines exist — the alternative would be paying out less on an unrelated booking with nothing in the response explaining it.

So the two halves only reconcile when you read them together:

original line:   fee column shows what that payout could fund
carry-over line: the remainder, negative, in a later payout
── the two sum to the fee that was actually due

More than one fee can be short at once

When the money being reconciled cannot cover every fee due, each fee takes what it can and the remainder is deferred. One booking can therefore produce several carry-over lines, of different kinds, across later payouts — so finding one carry-over does not mean you have found them all.

Payouts are not an accounting view

A payout line reports what a given payout moved, not what a booking owes. To see the fees due on a booking overall — read internal_fees on theBooking.

When the numbers do not add up

The decomposition on each /channel_payouts breakdown line is expected to account for the net:

channel_price − ota_commission − ota_payment_charge_commission − withheld_city_tax = net_channel_payout

Each term is sourced independently and none is derived from the others, so this is a check you can run rather than an identity that is guaranteed to hold. In practice it closes; the cases where it does not are known and listed below.

net_channel_payout_in_cents is always the amount the channel actually reported. Always use it as the amount, and treat the other terms as the channel's explanation of it.

The known case where it will not balance is Booking.com correction and fee true-up rows. Such a row restates a gross and a city tax that cancel each other out, then wires a small difference:

{
"channel_price_in_cents": 4854,
"withheld_city_tax_in_cents": 4854,
"ota_commission_in_cents": 0,
"ota_payment_charge_commission_in_cents": 0,
"net_channel_payout_in_cents": -68
}

The declared terms sum to 0.00; the channel wired −0.68. That difference is real money and the row reports it verbatim rather than hiding it in one of the other columns.

This example is a Booking.com line — it declares a payment-charge commission and a city tax, which is only ever true of Booking.com. On Airbnb the same row would carry null for the payment-charge commission and a cleaning fee instead.

null means "not known", not "nothing" — do not sum it as zero without deciding that is what you want. One deliberate exception on Airbnb: ota_payment_charge_commission_in_cents is always null because the channel has no such line, and there it genuinely is zero. See How each channel fills these terms.

What you can verify independently, and what you cannot

Not every field on a breakdown line has the same provenance, and the response does not currently distinguish them. This matters if you are reconciling against your own records.

FieldWhere it comes from
net_channel_payout_in_centsDeclared by the channel, passed through verbatim
ota_commission_in_centsDeclared by the channel (Booking.com), or its per-payout host fee (Airbnb)
channel_price_in_centsDeclared by the channel (Booking.com), or Smily's stored channel price (Airbnb)
withheld_city_tax_in_centsDeclared by Booking.com — but on Airbnb, reconstructed by Smily

Airbnb itemises neither the withheld city tax nor the channel price per payout. For Airbnb reservation movements Smily sources both from the booking's own records as they stood when that movement settled — the price the guest was charged, and the booking's withheld city-tax fees — and splits them across the movements of a multi-month or extended stay. They are our best reconstruction of what Airbnb kept, not figures Airbnb gave us.

Two consequences to code for. First, the split across movements is an allocation: for a stay settling in several movements, each movement's share is apportioned using the other terms, so an individual line's price and tax are our attribution rather than an independently attested figure — only their total across the stay is. Second, Airbnb movements that are not reservations (resolutions, adjustments, pass-throughs) carry no reconstruction at all and are served net-only, with these terms null.

The inputs to that reconstruction are not currently exposed on Bookings Fees, so you cannot reproduce the Airbnb figure from the API. If you need a number you can audit end to end, use net_channel_payout_in_cents.

Reconciling per booking, not per line

Breakdown lines are per movement, not per booking. A single booking can appear:

  • several times in one channel payout — for example a stay extension collected as an extra payment, or a refund or adjustment the channel applied just after wiring the original amount;
  • across several channel payouts over time — the monthly tranches of a long stay settle in separate payouts, as does an extension booked after the original settled;
  • again later as a correction or a resolution, with a negative net.

So a booking-level question needs every movement, not the one in front of you:

GET /channel_payouts?booking_id=<id>

That returns every channel payout whose breakdown touches the booking. Sum across all of them before concluding anything about the booking as a whole.

note

Per-line and per-booking reconciliation can disagree. A channel correction may restate a figure an earlier payout already settled — each line is individually faithful to what the channel reported, while the booking's lines sum to more than the booking was actually charged. Reconcile at the level you actually care about, and expect the two to differ on corrections.

Beta caveats

Both endpoints are in beta and their shape may still change.

  • /payouts returns one payout per page. Rows may be computed on the fly, which is expensive, so per_page is capped at 1 while in beta. Page through with page.
  • payment_gateway_id is required on every /payouts request; omitting it returns 400.
  • Both endpoints are read-only. Payouts and channel payouts are produced internally by Smily as OTA wires arrive; there is no way to create, update or delete them through the API.