How the integration fits together
The host app and flights miniapp do not call each other directly. The Boxo Platform mediates both directions. Consequently, all host-app inbound requests arrive from the Boxo Platform and use the host app’s credentials. All host-app outbound requests go to a Boxo Platform endpoint, which relays the request to the flights miniapp and returns its response.Endpoints exposed by the host app
The host app must expose the following endpoints and register each URL in the Boxo Dashboard. The platform authenticates with Basicbase64(hostapp_client_id:hostapp_secret_key), using the host-app credentials configured in the dashboard.
Boxo Platform endpoints called by the host app
Dashboard settings to confirm
Boxo must enable Event Bridge for both the host app and the flights miniapp. Enabling only one side prevents event delivery.
Authentication
Calls received by the host app (event receiver, create order payment, get payment status) carry:200 in all cases, using a response field to report problems; a non-200 response is treated as a delivery failure and retried.
Calls made by the host app to the Event Bridge API use the same Basic credentials. Payment platform endpoints such as complete-order are authorized by IP whitelisting or request signaturing configured in the dashboard.
IP whitelisting and request signaturing are also available for securing server-to-server calls. See Security measures.
What an event delivery looks like
Every flight order event arrives at the host app’s event receiver in the platform’s standard envelope:
Return HTTP
200. The response body is forwarded to the flights miniapp, but the flights event flow does not depend on its contents; {} is sufficient.
The five event types
The event type identifies the outcome and can be used for routing without inspecting the payload. All five lifecycle events require a settled host-app payment. At the time an event is first emitted,payment_status is PAID. A pending, cancelled, or failed payment never produces a lifecycle event.
Nothing is sent while an order is still moving — awaiting payment or mid-ticketing. Events communicate final outcomes rather than intermediate steps.
order.succeeded
Sent once the airline has issued the ticket. The host app should use this event to add or confirm the booking. booking_reference holds the PNR and booking contains the information required to render a booking card.
Some airlines issue asynchronously, so this can arrive minutes after the payment settled. It fires when the ticket genuinely exists, not when it was requested.
order.failed
The trip was not delivered — typically the airline refused the booking, or the traveller paid for a change the airline then would not make.
If money was taken and no ticket came back, order.refunded follows.
order.refunded
Sent when a refund obligation is opened, not when it later completes. The host app must return refund.amount in refund.currency against refund.source_order_payment_id, which is the order_payment_id originally issued by the host app.
refund.amount is already net of any airline penalty. penalty_amount is informational and must not be subtracted again.
refund.type is full when the refund returns the order’s whole total_amount, and partial otherwise — for example, when the airline kept a cancellation fee, or a change left a credit.
booking.changed
The trip moved. The payload carries the new flights. status is still BOOKED and miniapp_order_id is unchanged — this updates an existing booking, it is not a second ticket.
Fires whenever a ticketed trip changes, whoever changed it:
- a change the traveller confirms in the flights miniapp;
- a schedule change or other update from the airline, picked up from the airline’s notifications or the flights miniapp’s regular check of upcoming trips;
- a correction made by the support team.
payload.booking is actually different. A change of status is announced by its own event instead: a trip the airline cancels sends booking.cancelled, not booking.changed.
booking.cancelled
The trip has ended. If a refund is due, a separate order.refunded follows.
Payload reference
Thepayload object of every order event. All keys are always present. Missing data is "" or null, never an omitted key. Amounts are decimal strings, never numbers.
Top level
booking
No airline is named at this level on purpose: one trip can be flown by several carriers. Airlines live on segments.
Place
Unknown place → every field
"".
Names are included so the host app does not require its own IATA lookup table. They use one stable catalogue language rather than the traveller’s locale; host apps may localize them independently.
Passenger
Travel-document details entered at checkout are stored encrypted by the flights miniapp and are never sent to the host app.
Journey
Display
departing_at / arriving_at. Sort and compare on departing_at_utc / arriving_at_utc.
Segment
Seats belong to the flight they were bought for, so on a connecting trip one flight can list a seat while the next lists
[]. A flight retimed on the same day keeps its seats. A change that puts the traveller on a different flight, or moves a flight to another day, does not carry its seats over: the new flight lists [].
Carrier
All fields are
"" when unknown. A logo is supplied when available, so a separate host-app airline image library is not required.
price
refund — on order.refunded only
Delivery, retries, and idempotency
- Events are sent after the corresponding state change is committed, so delivered payloads describe committed state.
- A failed delivery is retried periodically, up to 5 attempts in total. After that it is flagged for operational review and can be re-sent once the host-app endpoint is healthy.
- Retried events are re-derived from current order state. If the order changes between attempts, the retry describes the current state rather than replaying stale data.
- Delivery failures never affect the booking. An unavailable host-app endpoint cannot prevent ticket issuance.
- Any response other than HTTP
200counts as a failure and is retried. Unsupported event types must also receive200.
Reading status
status is a one-word summary of two independent facts: whether payment settled and whether a ticket exists. All lifecycle events are emitted only after payment_status=PAID; the three status fields still remain in the payload for explicit state tracking.
The critical combination is
payment_status=PAID with fulfillment_status=FAILED: the traveller was charged but no ticket was issued. It produces order.failed, followed by order.refunded.Full example — order.succeeded
As delivered to the host-app event receiver:
order.refunded, payload.refund carries:
Request booking information — booking.info
The host app sends booking.info through the Boxo Event Bridge API. The platform relays the event to the flights miniapp and returns the flights miniapp response as the response body. This operation can backfill state after an outage or retrieve a booking list without storing every lifecycle event.
POST /api/v1/events/hostapp/ on the Boxo Platform.
Response
refund is null here.
An unknown
user_id returns an error rather than an empty list, preventing a mismatched reference from appearing to be a traveller with no trips.
Request offer availability — order.offer.info
The host app can check whether the fare behind an order is still bookable by sending another Event Bridge request:
payload.miniapp_order_id is required. The order is scoped to the authenticated miniapp; an unknown, malformed, or other miniapp’s id returns a validation error.
expires_in is the number of whole seconds left until expires_at at the moment the response was sent, rounded down, and 0 once the fare has expired. Counting down from it on receipt keeps a timer correct even when the device clock is not; comparing expires_at with a phone set a few minutes out would show the wrong time left.
status is available while expires_in is above 0, otherwise expired. This is a time-based pre-check; a supplier can still withdraw inventory or change a fare before its stated expiry.
Payments
Flight checkout uses the standard Boxo Payments flow with three host-app integration points.Create order payment request
The Boxo Platform calls the host app’s create order payment endpoint. The body carriesorder.miniapp_order_id, which is the same id included in every lifecycle event for that order and should be stored with the payment.
{"order_payment_id": "..."} with HTTP 200. The traveller settles that payment through the SDK. If a refund is later required, the same id appears as refund.source_order_payment_id.
amount in currency is what the traveller pays, and is the figure to show and charge. When the order is charged in the traveller’s local currency, amount is the rounded local total and every other amount in order is in that currency too.
subtotal_amount + tax_amount always equals amount exactly; any rounding is absorbed by subtotal_amount. When a fare comes without a separate tax figure, or the payment is for a change to an existing trip, there is nothing to split: subtotal_amount equals amount, tax_amount is 0, tax_title is empty, and taxes_included is true.
Report the payment result
After the traveller confirms, call the platform:
Booking starts after the payment result is acknowledged, not during the request.
order.succeeded, rather than the payment-completion response, is the signal that a ticket exists.
A failed notice for a payment already recorded as settled is ignored as an out-of-order redelivery.
Get order payment status
If a payment result is not delivered, the flights miniapp queries the Boxo Platform, which calls the host app’s get order payment status endpoint. The endpoint must return the current status (in_process, paid, cancelled, or failed).
This endpoint is the recovery path for lost payment notifications. A missing or incorrect implementation can leave a charged traveller without a ticket or refund.
Payment custom attributes
order.custom_attributes carries only what the host needs to render the first charge: the fare’s expiry, the quoted trip as offer, and price. A booking does not exist yet — it is created after the host marks the payment paid, and arrives on order.succeeded as booking.
offer uses the same itinerary keys as that later booking object (origin, destination, journeys, passengers, …) so the payment sheet and the booking card share a parser. It does not include reference: there is no PNR until the airline confirms.
Lifecycle fields (miniapp_order_id, status, payment_status, fulfillment_status, booking_reference, refund, …) are omitted.
When an order is also priced in the traveller’s own currency, the same object carries a local-pricing audit trail:
The charge amount is always
order.amount, which is the rounded local total when the order is charged in local currency. The additional pricing keys provide an audit trail after exchange rates change.
offer_expires_at is the supplier’s own expiry for the fare wherever the supplier publishes one. Where it does not, the flights miniapp uses a conservative expiry 25 minutes after the fare was last confirmed, inside the supplier’s booking window. Orders that have neither fall back to 30 minutes from order creation. A payment for a change to an existing trip carries the expiry of the change quote.
For a Price Valid timer, count down from offer_expires_in starting when the request arrives; the timer then does not depend on the device clock. To restore the timer later, for example after the app is reopened, request order.offer.info for a fresh count.
Traveller identity
Login uses Boxo Connect through either the OAuth or Direct flow. In both flows,reference in the supplied user data is the relevant identity field.
reference becomes the traveller’s identity in the flights miniapp. It is sent as user_id in booking.info and echoed as hostapp_user_id in the create order payment request.
When supplied, first_name, last_name, email, and phone are stored with the traveller and used to prefill checkout. Send phone in E.164 format, e.g. +14155552671. A number that is not a valid phone number is set aside rather than failing the login. A field left out at a later login keeps the value already stored.
Integration checklist
1
Enable Event Bridge
Request that Boxo enable Event Bridge for the host app and flights miniapp, and register the host-app event receiver URL.
2
Register payment endpoints
Register the host-app create order payment and get order payment status URLs. Both are required for safe checkout.
3
Verify inbound credentials
Verify Basic
hostapp_client_id:hostapp_secret_key on every inbound call.4
Acknowledge every event
Return
200 from the event receiver for every event, including unsupported types.5
Route on event_type
Route on
event_type. Do not reduce the five types to one generic status update.6
Key records on miniapp_order_id
Key records on
payload.miniapp_order_id and make handlers idempotent.7
Render booking cards from the payload
Render booking cards from
payload.booking: names, logos, wall-clock times, terminals, seats, and PNR. No host-app lookup tables are required.8
Treat booking.changed as an update
Treat
booking.changed as an update to an existing booking, never as a new one.9
Refund the net amount
On
order.refunded, return refund.amount — already net — against refund.source_order_payment_id.10
Read payment and fulfillment together
Read
payment_status and fulfillment_status together, not just status.11
Keep Connect reference stable
Keep
reference stable per user at connect.Quick reference
Platform reference: Boxo Event Bridge, Boxo Payments, Boxo Connect.