Creating the subscription
When a traveller selects protection and completes their booking, your application creates the subscription, their policy, with one HTTP POST to Koala. It is the only call your application makes to Koala directly: the widget assembles the request, you fill in the few values only you know and send it once your booking is paid.
The same mechanism covers the bookings where the traveller declined the offer: the very same POST then deletes the quote instead of creating a subscription, so Koala can measure the offer's performance.
The widget never sends this for you. It only prepares the payload. Making the call, after your own booking is paid, is yours to do.
When to send it
One precondition: your booking is paid. What the call does depends on what the traveller chose:
- They chose one or more Koala bundles and completed the booking: the call creates the subscription, their policy.
- They chose nothing and completed the booking: the call deletes the quote, so Koala measures the offer's conversion against all bookings, not only the ones that took protection.
- They abandoned the booking: send nothing.
Send the request on every paid booking, with or without a selection. Skipping the bookings where nothing was selected would make conversion look artificially high.
One behavior, two request shapes
The prepared request takes one of two shapes, and you never choose between them: the widget keeps book matching
the current basket:
- protection selected:
msgRawis a subscription request; - nothing selected:
msgRawis a deleted-quote request.
Your integration is the same in both cases: fill every [TOKEN] placeholder in msgRaw, then POST it to fullUrl as
JSON. The deleted-quote request carries only [BOOKINGNUMBER], so it is the same substitution on a smaller payload.
What the widget gives you
The basket carries the book object (on basketPayload.book, and on the onBasket event data). Its full shape, both
request shapes in msgRaw, and every [TOKEN] placeholder you fill in are documented in the reference page
Request template. Apart from [BOOKINGNUMBER], the deleted-quote request is
already complete: leave the rest of it as prepared.
No token appears twice in msgRaw, so you can substitute across the whole body without a value landing on the wrong
person; the naming rules behind this are on the same reference page. Everything else about the travellers (their number,
age bands, residence, bag counts) is already filled from the quote: leave it as prepared.
Authentication
The endpoints take no credentials, and the call needs no authentication header. The quote reference inside the payload was generated by Koala when the trip was quoted, and it is what authenticates the request. Treat the prepared payload accordingly: pass it through your systems, but keep it out of logs and anywhere else it could leak.
Making the call
The read and the send sit on opposite sides of your stack. The browser holds the prepared book; your backend is where
the booking is confirmed and paid. So the flow has two steps: your page passes book to your backend along with the
booking, and your backend sends the request once the booking is done.
Step 1, on your page: when the traveller submits the booking, include the prepared book in the request to your
backend:
// The prepared payload lives on the page. Send it to your backend with the booking.
const { book } = window.CTStore.koala.basketPayload;
await myApi.submitBooking({
// ...your own booking data...
koalaBook: { msgRaw: book.msgRaw, fullUrl: book.fullUrl },
});
Step 2, on your backend: process the booking as usual. Once it is confirmed and paid, fill the placeholders and make the POST:
// On your backend, after the booking is confirmed and paid.
const { msgRaw, fullUrl } = booking.koalaBook;
// Every token is unique, so replacing them across the whole body is safe.
// A deleted-quote request has only [BOOKINGNUMBER].
const body = fillPlaceholders(msgRaw, {
'[BOOKINGNUMBER]': booking.reference,
'[CUSTOMER_FIRSTNAME]': booking.customer.firstName,
'[CUSTOMER_LASTNAME]': booking.customer.lastName,
'[CUSTOMER_EMAIL]': booking.customer.email,
'[CUSTOMER_LANGUAGE]': 'en-GB',
// One pair per traveller, in the order you quoted them.
'[TRAVELER_1_FIRSTNAME]': booking.travellers[0].firstName,
'[TRAVELER_1_LASTNAME]': booking.travellers[0].lastName,
'[TRAVELER_2_FIRSTNAME]': booking.travellers[1].firstName,
'[TRAVELER_2_LASTNAME]': booking.travellers[1].lastName,
// Card details too, if Koala is your merchant of record.
});
await fetch(fullUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
fillPlaceholders is your own helper, and it can be as simple as a replace over the serialized body:
function fillPlaceholders(msgRaw, values) {
const filled = Object.entries(values).reduce(
(json, [token, value]) => json.split(token).join(value),
JSON.stringify(msgRaw),
);
return JSON.parse(filled);
}
Generate the traveller entries from your own list rather than hard-coding two, and if the booking is never completed, your backend simply sends nothing.
Capturing it from the event
Step 1 reads book from the basket at submit time. If you would rather capture it as the traveller goes, listen for
onBasket and keep the latest payload to hand to your backend:
// On your page: keep the latest prepared payload as the basket changes.
window.CTStore.koala.events.onBasket = [
(status, data) => {
// `data` is the Koala basket; `data.book` is the prepared payload.
myApp.latestKoalaBook = data.book;
},
];
After you send it
- Creating the subscription returns
201with the created policies: your booking number echoed back, one contract per selected bundle, and the total price. Keep each contract'sidwith your booking; it identifies the policy when you interact with Koala about it. The full shape is in the reference page Request template. - Deleting the quote returns
204with no body.
Send the request once per paid booking. The two shapes behave differently when a duplicate arrives:
- a duplicate subscription request cannot create a second policy: it is rejected with
409, because its booking number and its quote have already been used; - a duplicate deleted-quote request is not rejected, and every extra one skews the conversion measurement.
Handling failures and retries
Read the status code:
201or204: done, stop.409(subscription request): the booking number or the quote was already used. When it shows up while retrying, an earlier attempt landed: treat it as success and stop.400: the request itself is invalid, so an identical retry fails identically. Log the response body, check the values you filled in, and contact Koala if the cause is unclear.5xx, or the request never got through: transient, retry.
No traveller is waiting on this call, so retry calmly: a few attempts with increasing delays over a few minutes is
plenty. Retrying the subscription request is safe: duplicates are rejected, so the retry loop always converges on
201 or 409, and both mean the policy exists.
The deleted-quote request is the one to retry carefully, since duplicates count. Retry it only when you know the
first attempt was not processed: the connection failed, or Koala answered 5xx. On a timeout, where the request may
have landed, do not retry: one missing deletion skews the measurement less than a double count.