Cuppa Embed — Low-Code Integration Guide

Overview

Cuppa Embed adds a travel eSIM to your booking extras with one HTML tag and one server call. Cuppa shows the eSIM at the price you set; your cart, checkout and payment processor stay exactly as they are.

Booking extras

Additional driver          +$25.00
Child seat                 +$15.00
Travel eSIM                Powered by Cuppa
  Stay connected during your trip to Japan.
  1 GB · 7 days
  $10.00
  [ Add eSIM ]

Booking total              $200.00
You buildCuppa handles
One HTML tag on your extras pageShowing the eSIM plans at your prices
A few lines that add the selected eSIM to your cartBilling your business the wholesale price
One server call after the booking is paidBuying, provisioning and emailing the eSIM

The component only handles selection. It never collects the traveller's card details and never talks to your payment processor. The traveller pays you; Cuppa bills your business separately.

If you would rather build the picker yourself, use the Full API. Both use the same fulfilment call.

How it works

  1. The traveller taps Add eSIM in the Cuppa component on your booking page.
  2. Your page adds the eSIM to your cart.
  3. The traveller pays you as usual, through your existing checkout.
  4. Your server calls POST /v1/fulfilments once the booking is paid.
  5. Cuppa bills your business, provisions the eSIM and emails it to the traveller.

Before you begin

Set up four things in your Cuppa account:

  • The plans you want to sell and your retail price for each, for example $10.00
  • A company card on file, or a prepaid credit balance
  • Your branding for the traveller's activation email
  • The site origins where the component may load, for example https://book.example.com

You will receive two keys.

KeyExampleWhere it belongs
Publishable keypk_test_EXAMPLEIn your page HTML. Safe to expose; it only works on your approved origins and can only display your plans.
Secret keysk_test_EXAMPLEOn your server only. It requests eSIMs that are billed to your business. Never place it in HTML or JavaScript.

Component script: https://js.cuppa.so/v1/embed.js

REST API base: https://api.cuppa.so/v1

Step 1: Add the component

Place the tag where the eSIM should appear among your extras, and load the script anywhere on the page.

<script async src="https://js.cuppa.so/v1/embed.js"></script>

<cuppa-esim
  publishable-key="pk_test_EXAMPLE"
  destination="JP"
  currency="USD">
</cuppa-esim>

There is no initialisation code and no load order to manage. The tag is inert until the script arrives, then renders itself. Set destination to the traveller's arrival or pick-up country.

The component fails quietly. If Cuppa is unreachable, or you sell no plan for the destination, it collapses to zero height and your booking flow continues without it.

Step 2: Add the selection to your cart

When the traveller adds or removes an eSIM, the component tells your page which plan was chosen and its price. Add it to your cart like any other extra, and store the offerId against the booking. Pick whichever option suits your page.

Option A: JavaScript event

Best when your booking total updates on the page without a reload.

const esim = document.querySelector("cuppa-esim");

esim.addEventListener("cuppa:change", (event) => {
  const { offerId, plan, price } = event.detail;

  if (offerId) {
    // Your own cart code.
    bookingCart.setExtra("esim", {
      offerId,
      label: plan.name,
      amountMinor: price.amountMinor
    });
  } else {
    bookingCart.removeExtra("esim"); // traveller removed the eSIM
  }
});

The event detail when an eSIM is added:

{
  "offerId": "offer_jp_1gb_7d",
  "destination": "JP",
  "plan": { "name": "Japan 1 GB · 7 days", "dataMb": 1024, "validityDays": 7 },
  "price": { "amountMinor": 1000, "currency": "USD" }
}

When the traveller removes the eSIM, offerId is null.

Option B: Hidden form field

Best when your extras page is a classic HTML form that posts to your server. Add input-name and place the tag inside your form; no JavaScript is needed.

<form method="post" action="/booking/extras">
  <!-- your other extras -->
  <cuppa-esim
    publishable-key="pk_test_EXAMPLE"
    destination="JP"
    currency="USD"
    input-name="cuppa_offer_id">
  </cuppa-esim>
  <button type="submit">Continue</button>
</form>

The component keeps a hidden cuppa_offer_id field up to date. Your server reads it when the form is submitted; an empty value means no eSIM.

Pricing the cart line

The price is the fixed retail price you set in your Cuppa account, so it is the same for every traveller. As with any extra, have your server decide the amount rather than trusting a figure sent by the browser. Either keep your eSIM prices alongside your other extras, or look them up with GET /v1/offers/{offerId}.

If the traveller returns to the extras page, render the tag with offer-id="offer_jp_1gb_7d" so the eSIM shows as already added.

Step 3: Request the eSIM after payment

Once your own backend has confirmed the booking is paid, tell Cuppa. This is the only server call in the integration.

async function fulfilPaidBooking(booking) {
  if (booking.paymentStatus !== "paid") return;
  if (!booking.esimOfferId) return; // no eSIM on this booking

  const response = await fetch("https://api.cuppa.so/v1/fulfilments", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.CUPPA_SECRET_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      orderId: booking.id,
      itemId: "esim-1",
      offerId: booking.esimOfferId,
      customerEmail: booking.customer.email,
      customerName: booking.customer.name
    })
  });

  if (!response.ok) throw new Error("eSIM fulfilment needs attention");
  return response.json();
}

Cuppa replies 202 Accepted:

{
  "fulfilmentId": "ful_3Xa9Lw7c",
  "status": "processing",
  "orderId": "BK-104233",
  "itemId": "esim-1",
  "offerId": "offer_jp_1gb_7d",
  "wholesaleAmountMinor": 400,
  "currency": "USD"
}

Cuppa then bills your business the wholesale price, buys the eSIM and emails the traveller.

  • Call from your server, at the point you know you have been paid. That is usually your payment processor's server-side confirmation. Never trigger it from a browser success page.
  • Retries are safe. orderId is your booking reference and itemId names one eSIM within it. Together they identify one eSIM on your account. Sending the same pair again returns the existing fulfilment; it never bills or provisions twice.
  • Several eSIMs on one booking. Make one call per eSIM, each with its own itemId (esim-1, esim-2) and, if you wish, its own customerEmail.
  • Conflicts are rejected. The same orderId and itemId with a different offerId returns item_conflict.
  • 202 is an acknowledgement, not delivery. See Step 4.

This is the only call you must make. The status and offer endpoints described below are optional.

Step 4: Check the outcome

Most fulfilments reach delivered within seconds and need nothing further from you. To check one, call GET /v1/fulfilments/{fulfilmentId}.

StatusMeaningWhat you do
processingCuppa is billing your account and provisioning the eSIMNothing
deliveredThe activation email has been sent to the travellerNothing
action_requiredYour card was declined or your credits ran out. No eSIM has been boughtUpdate your card or top up; Cuppa then resumes automatically
failedCuppa could not provision the plan and has reversed the wholesale chargeRefund the traveller's eSIM line, or offer another plan

Cuppa also emails your account contact whenever a fulfilment becomes action_required or failed, so you do not need to poll to catch problems.

If provisioning fails

  • Cuppa retries first. A supplier error is retried automatically before a fulfilment is marked failed. Each fulfilment carries one supplier reference, so a retry can never buy a second eSIM.
  • Your money is returned automatically. On failed, Cuppa refunds the wholesale charge to your card or restores your credits. No request is needed.
  • failed is final. Re-sending the same orderId and itemId returns the failed fulfilment. To try again or offer a different plan, send a new itemId.
  • Reconciliation. Every fulfilment reports its billing state, and your Cuppa statement lists each one by orderId and itemId so it can be matched to your bookings.
{
  "fulfilmentId": "ful_3Xa9Lw7c",
  "status": "failed",
  "orderId": "BK-104233",
  "itemId": "esim-1",
  "billing": { "status": "refunded", "amountMinor": 400, "currency": "USD" }
}

billing.status is pending, charged, refunded or not_charged.

Reference

Component attributes

AttributeRequiredDescription
publishable-keyYesYour pk_test_... or pk_live_... key
destinationYesISO 3166-1 two-letter country code, e.g. JP. Changing it reloads the plans and clears any selection
currencyYesISO 4217 code matching your booking currency, e.g. USD
offer-idNoShows this plan as already added when the page is rendered again
input-nameNoName of a hidden form field the component keeps in sync with the selected offerId
themeNoauto (default, follows your page), light or dark

Component events

Events are dispatched on the <cuppa-esim> element and bubble.

EventFires whenevent.detail
cuppa:readyPlans have loaded and the component is visible{ destination }
cuppa:changeThe traveller adds, changes or removes an eSIMThe selection shown in Step 2, or { offerId: null } on removal
cuppa:errorPlans could not be loaded{ code, message }

The picker renders inside an isolated Cuppa-hosted frame, so your page styles cannot break it and it cannot read your page.

API endpoints

All requests use HTTPS and JSON, and authenticate with Authorization: Bearer sk_.... Amounts are integers in the currency's minor unit (1000 = $10.00).

Method and pathRequired?Purpose
POST /v1/fulfilmentsYesRequest the eSIM for a paid booking
GET /v1/fulfilments/{fulfilmentId}OptionalRead a fulfilment's status and billing state
GET /v1/offers/{offerId}OptionalRead a plan's name, retail price and wholesale price

POST /v1/fulfilments body

FieldRequiredDescription
orderIdYesYour booking reference. Shown on your Cuppa statement
itemIdYesYour name for this eSIM within the booking, e.g. esim-1. orderId and itemId together must be unique on your account
offerIdYesThe plan the traveller selected
customerEmailYesWhere the activation email is sent
customerNameNoUsed to address the email

Errors

Errors return a non-2xx status and a body of the form { "error": { "code": "offer_not_found", "message": "..." } }.

HTTPCodeMeaning
401invalid_keyMissing, wrong or revoked secret key
404offer_not_foundNo such plan on your account, or it is no longer sold
409item_conflictThis orderId and itemId already have a fulfilment for a different plan
422invalid_requestA required field is missing or malformed
429rate_limitedToo many requests. Retry after the Retry-After interval
5xxserver_errorRetry with the same orderId and itemId

A declined company card is not an API error. The request is accepted and the fulfilment moves to action_required.

Pricing and billing

Every plan has two fixed prices. There is no dynamic pricing and nothing to quote.

PriceSet byExample
Retail priceYou, in your Cuppa account$10.00, shown in the component and charged by you to the traveller
Wholesale priceCuppa$4.00, billed to your business for each eSIM fulfilled

In this example you keep $6.00 per eSIM. Each plan you sell is set up in one of two ways:

  • Paid add-on. The traveller pays your retail price, which must be at or above the wholesale price.
  • Included eSIM. You fund the eSIM as part of the booking. The component shows it as included, the traveller pays $0, and Cuppa bills you the wholesale price. The retail minimum does not apply. Ask your Cuppa contact to enable this for a plan.

Price changes:

  • Your retail price takes effect in the component immediately. Bookings already in checkout keep the price in your cart.
  • The wholesale price never rises without 30 days' written notice. You are billed the wholesale price in force when you call POST /v1/fulfilments, and the response states the amount. A booking paid today is therefore never billed more than the price published today.

How Cuppa bills you

  • Pay as you go. Cuppa charges your saved company card for each eSIM before provisioning it. No upfront cost.
  • Prepaid credits. Cuppa deducts each eSIM from your credit balance, with no card charge per eSIM.

Cuppa never buys an eSIM before your business has been billed, and never bills twice for one booking.

What the traveller receives

An activation email with a QR code and install instructions, carrying your name, logo, colours and wording. To deliver the eSIM through your own channels instead, see the Full API guide.

Test mode and go-live checklist

Build against pk_test_... and sk_test_... keys. In test mode there is no card charge and no supplier purchase. Cuppa sends a clearly labelled TEST activation email, with non-functional activation data, to authorised test recipients only.

Going live means swapping both keys for their pk_live_... and sk_live_... equivalents.

  • The component renders on the extras page, and the page still works when it is absent
  • Adding and removing an eSIM updates your cart correctly
  • Changing the destination clears the previous selection and its cart line
  • The offerId is stored against the booking
  • Your server, not the browser, decides the amount charged for the eSIM line
  • Only bookings confirmed paid by your server trigger the fulfilment call
  • Repeated payment callbacks send the same orderId and itemId
  • A failed fulfilment leads to a refund of the traveller's eSIM line
  • action_required and failed emails reach someone who can act on them
  • The activation email branding has been reviewed with a TEST email
  • Your live site origins are approved in your Cuppa account