Collecting Premiums on Your Own Platform (Connect Distributor)
This guide is for partners who sell Sitata insurance inside their own booking portal and collect payment through their own Stripe Connect platform, while Sitata remains the merchant of record for the premium. Your traveller enters their card once in your checkout; the premium appears on their statement as a separate charge from Sitata.
If you instead collect payment entirely yourself (you are the merchant of record) or use Sitata's checkout, see the standard purchase guide.
How it works
You create two PaymentIntents on your own Stripe platform with your own API keys:
- Your trip charge โ exactly as you do today. Your statement descriptor, your merchant of record, your supplier splits.
- The insurance premium โ a separate PaymentIntent with three extra parameters that make Sitata the settlement merchant and move the funds automatically:
{
"amount": "<premium_amount from the payment session>",
"currency": "<currency_code from the payment session>",
"on_behalf_of": "<SITATA_ACCOUNT_ID>",
"transfer_data": { "destination": "<SITATA_ACCOUNT_ID>" },
"application_fee_amount": "<commission_amount from the payment session>"
}
Because both PaymentIntents are on your platform, the card is entered once โ
the resulting PaymentMethod is confirmed against both PaymentIntents.
on_behalf_of puts Sitata's statement descriptor on the premium charge
(Sitata is the card-network merchant of record), and your commission stays
with you at source via the application fee. Sitata receives the net
amount (premium minus your commission) automatically.
Two charges, one card entry
These are two separate charges โ a merchant of record is a property of a charge, so a single charge cannot be split between you and Sitata. The traveller types their card once, but sees two lines on their statement (your descriptor for the trip, Sitata's for the premium), and each charge authorizes independently.
Note that a Stripe Elements form confirms one PaymentIntent โ it cannot take two. What bridges the two PaymentIntents is the PaymentMethod the form produces. Two standard integrations:
- Collect first, confirm both server-side (recommended). Use Elements
only to tokenize:
stripe.createPaymentMethod(...)โ send thepm_...to your server โ attach it to a Customer โ create and confirm both PaymentIntents server-side (payment_method: pm_..., confirm: true). If either confirmation returnsrequires_action(3DS), runstripe.handleNextAction(clientSecret)on the client for that PaymentIntent โ possibly once per charge, sequentially. - Elements confirms the trip, server confirms the premium. Initialize
Elements with the trip PaymentIntent's client secret and confirm it with
setup_future_usage: "off_session"(attaching the PaymentMethod to the Customer), then confirm the premium PaymentIntent server-side with that PaymentMethod, surfacingrequires_actionto the client if the issuer demands a second challenge.
Decide up front how your checkout handles a partial failure โ e.g. trip succeeds but the premium declines โ typically by offering an insurance payment retry rather than voiding the booking.
Sitata never charges the traveller in this flow. Policies are issued only after Sitata verifies, on its own Stripe account, that your payment arrived.
What you need from Sitata (one-time setup)
| Item | Purpose |
|---|---|
Sitata's Stripe account id (acct_...) | Referenced in on_behalf_of and transfer_data.destination. Established by connecting Sitata's Stripe account to your platform (send us your Connect onboarding link; our finance team approves). |
Your company_id and company API token | Authentication for the /api/v2/org/:company_id/... endpoints. |
| Product identifiers | The products you are licensed to sell, used in quote requests. |
Sitata must also activate your account for this flow (the
connect_distributor package and an active payment gateway account) and
configure your commission percentage โ contact your account manager.
warning
Stripe restricts on_behalf_of to platforms and connected accounts in the
same region. Confirm regional compatibility with Sitata before building.
Quote and bind flow
1. Quote
POST /api/v2/products/quote โ the standard quote endpoint, authenticated as
your company. Send trip destinations and dates, traveller details,
currency_code, and product ids. The response is your quote: its id
and the full premium to display to the traveller. No payment intent is
created.
2. Create a payment session
POST /api/v2/org/:company_id/payment_sessions
{ "quote_id": "<the quote id from step 1>" }
Response:
{
"payment_session": {
"id": "ps_...",
"quote_id": "...",
"premium_amount": 10000,
"commission_amount": 2000,
"net_amount": 8000,
"currency_code": "USD",
"status": 0
}
}
This is your exact charging instruction: charge premium_amount in
currency_code with application_fee_amount = commission_amount, so that
exactly net_amount arrives on Sitata's account. The call is idempotent per
quote โ repeating it returns the same session (amounts refresh if the quote
was re-priced, while still unconfirmed).
3. Charge the traveller (your side)
In your checkout, confirm both PaymentIntents (trip + premium) against the traveller's card. The premium PaymentIntent uses the parameters shown above.
caution
The premium must be charged in the session's currency_code and for exactly
premium_amount, with the application fee exactly commission_amount. Any
mismatch in the amount arriving on Sitata's account fails verification and no
policy is issued.
4. Find the payment id on Sitata's account
When your premium charge succeeds, Stripe automatically moves the net premium
to Sitata and creates a payment object on Sitata's account. That object
has its own id, starting with py_... โ and that is the id you confirm with
in step 5. Your own pi_... / ch_... ids won't work, because Sitata can
only see and verify objects on its account.
You can read the py_... id in a single API call with your own keys, by
retrieving your premium PaymentIntent with an expand parameter:
curl https://api.stripe.com/v1/payment_intents/{PREMIUM_PAYMENT_INTENT_ID} \
-u sk_your_secret_key: \
-d "expand[]=latest_charge.transfer.destination_payment"
Then read it from the response at:
latest_charge.transfer.destination_payment.id โ "py_..."
The path simply follows the money: your charge โ the automatic transfer it created โ the payment that transfer deposited on Sitata's account.
5. Bind โ confirm the payment
POST /api/v2/org/:company_id/payment_sessions/:id/confirm_payment
{ "payment_id": "py_..." }
Sitata fetches that payment on its own account and verifies it: paid, not
refunded, currency and amount exactly net_amount, arrived via a platform
transfer, and never used for another sale. On success the policies are issued
immediately (travellers receive their confirmations by email) and the session
returns with status: 1 (captured).
On verification failure the session returns status: 2
(verification_failed) with a failure_reason, and you may retry with a
corrected payment id. Nothing is issued until verification passes. A given
payment id can only ever issue one set of policies, and repeated confirms of
a captured session are rejected โ the call is safe to retry.
6. Status
GET /api/v2/org/:company_id/payment_sessions/:id โ poll session state at
any time.
Refunds and cancellations
The premium charge lives on your platform, so refunds are always executed on your side. There are two directions:
You initiate (traveller cancels with you)
Refund the traveller's premium charge on your platform with
reverse_transfer: true (and refund_application_fee at your discretion โ
whether you return your commission is between you and your Sitata agreement).
The transfer reversal automatically returns the net premium from Sitata's
account. No Sitata API call is needed: Sitata detects the reversal,
cancels the policies, and emails the travellers. Partial refunds are
supported and are applied proportionally.
Sitata initiates (traveller cancels with Sitata)
If a traveller cancels their policy through Sitata (or Sitata must cancel
it), Sitata cannot move the money โ you own the charge. Sitata sends a
connect_premium_refund_requested webhook to your configured endpoint:
{
"event": 912,
"payment_session_id": "ps_...",
"quote_id": "...",
"external_payment_id": "py_...",
"net_amount": 8000,
"currency_code": "USD",
"subscription_ids": ["..."]
}
On receiving it, execute the refund on your platform (as above, with
reverse_transfer). Reconciliation then completes automatically through the
same reversal detection. Your partner agreement defines the SLA for acting on
refund requests.
Commercial notes
- Your platform bears Stripe's processing fee on the premium charge
(standard destination-charge mechanics), so your effective margin is
application_fee_amountminus the Stripe fee. - Disputes on the premium charge route to Sitata (consistent with Sitata being merchant of record); disputes on your trip charge route to you.