Skip to main content

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:

  1. Your trip charge โ€” exactly as you do today. Your statement descriptor, your merchant of record, your supplier splits.
  2. 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 the pm_... to your server โ†’ attach it to a Customer โ†’ create and confirm both PaymentIntents server-side (payment_method: pm_..., confirm: true). If either confirmation returns requires_action (3DS), run stripe.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, surfacing requires_action to 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)

ItemPurpose
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 tokenAuthentication for the /api/v2/org/:company_id/... endpoints.
Product identifiersThe 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_amount minus 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.