Put Terudo in your flow

Recipes to offer an eSIM at the right moment of a travel business's flow (booking confirmation, visa approval, trip page, customer area, emails, mobile apps) and in each framework (React, Next.js, Vue, Angular, WordPress, Webflow).

The best moment to offer an eSIM is when the traveller's trip becomes real: the booking is confirmed, the visa is approved, the itinerary is sent. You already know the destination and the dates; pass them, and Terudo sizes the plan to the trip.

Pick the element

Page in your flow Element Why
Booking or payment confirmation booking-banner One discreet line, opens into the full offer
Visa approved, itinerary, trip page trip-offer The recommended plan, first thing seen
Destination or country guide plans (with days if you know the length) Every plan of the country
Travel shop, "eSIM" menu entry destination-picker + plansInto Browse all destinations
Customer area, "My trip" esim-status The eSIM bought, its usage, install buttons
Email, SMS, chat, mobile app API: quote then order, send checkout_url No JavaScript there

Booking or visa confirmation (server-rendered page)

Render the trip you know, and an element session so your discount, reference and return page are trusted:

// Server (Node), when rendering the confirmation page
const res = await fetch('https://terudo.com/api/partner/v1/element_sessions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.TERUDO_SECRET_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    reference: booking.id,
    client_email: booking.email,
    discount_percent: 5,
    trip: { destination: booking.countryCode, start_date: booking.arrival, end_date: booking.departure },
    return_url: `https://www.your-agency.example/bookings/${booking.id}`,
    locale: booking.language,
  }),
});
const { client_token } = await res.json();
<script src="https://terudo.com/elements/v1.js" data-key="pk_live_…" data-session="{{ client_token }}" defer></script>
<div data-terudo="booking-banner" data-destination="{{ country }}" data-start="{{ arrival }}" data-end="{{ departure }}"></div>

If the session cannot be created (network), render the element without it: it still works at public prices and asks for the email.

React

import { useEffect, useRef } from 'react';

function loadTerudo() {
  if (window.Terudo) return Promise.resolve(window.Terudo);
  return new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = 'https://terudo.com/elements/v1.js';
    s.onload = () => resolve(window.Terudo);
    s.onerror = reject;
    document.head.appendChild(s);
  });
}

export function TripOffer({ destination, start, end, email, session, onCheckout }) {
  const ref = useRef(null);
  useEffect(() => {
    let cancelled = false;
    loadTerudo().then((Terudo) => {
      if (cancelled || !ref.current) return;
      const terudo = Terudo({ key: process.env.NEXT_PUBLIC_TERUDO_PK, session, locale: 'en' });
      terudo.mount('trip-offer', ref.current, { destination, start, end, email, onCheckout });
    });
    return () => { cancelled = true; };
  }, [destination, start, end, email, session, onCheckout]);
  return <div ref={ref} />;
}

Load the script without data-key when you mount from JavaScript, so nothing mounts twice.

Next.js (App Router)

Create the session in a Server Component and pass the token to a Client Component that mounts the element (as above, marked 'use client'). The publishable key can be NEXT_PUBLIC_…; the secret key stays in server code only. Use next/script with strategy="afterInteractive" if you prefer it to the loader above.

// app/bookings/[id]/page.tsx (Server Component)
const session = await fetch('https://terudo.com/api/partner/v1/element_sessions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.TERUDO_SECRET_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ reference: booking.id, client_email: booking.email, trip: { destination: booking.country, start_date: booking.from, end_date: booking.to } }),
  cache: 'no-store',
}).then((r) => r.json());

return <TripOffer destination={booking.country} start={booking.from} end={booking.to} session={session.client_token} />;

Vue 3

<script setup>
import { onMounted, ref, watch } from 'vue';
const props = defineProps({ destination: String, start: String, end: String });
const host = ref(null);
let offer;
onMounted(async () => {
  await new Promise((r) => { if (window.Terudo) return r(); const s = document.createElement('script'); s.src = 'https://terudo.com/elements/v1.js'; s.onload = r; document.head.appendChild(s); });
  offer = window.Terudo({ key: import.meta.env.VITE_TERUDO_PK }).mount('trip-offer', host.value, { ...props });
});
watch(() => ({ ...props }), (next) => offer?.update(next));
</script>
<template><div ref="host" /></template>

Angular

Load the script once (in index.html without data-key), then in a component: ngAfterViewInit() { this.offer = (window as any).Terudo({ key: environment.terudoPk }).mount('trip-offer', this.host.nativeElement, { destination, start, end }); } and call this.offer.update({...}) in ngOnChanges.

Single-page apps with the declarative tags

When you navigate without reloading, call window.terudo.autoMount() after the new view is in the DOM. Already mounted elements are skipped.

WordPress, Webflow, Wix, Shopify (no code)

Paste the script tag in the site's footer code (once), then an HTML block with the <div data-terudo=…> where the offer goes. List the site in your publishable key's sites. For a page per destination, use plans with that destination.

Customer area

Store order.esim.id (from the API or the order.delivered webhook) with the booking, then:

<div data-terudo="esim-status" data-esim-id="{{ esim_id }}"></div>

Emails, SMS, chat, mobile apps

No JavaScript runs there. From your server:

  1. POST /quotes with destination and dates: take the option with recommended: true.
  2. POST /orders with that plan, the traveller's email, your reference and return_url. The answer has checkout_url.
  3. Put checkout_url in your email, SMS or button. Or pass notify_client: true and Terudo emails the payment link itself, in the traveller's language.

Reusing the same reference returns the same order, so sending the email twice never makes two orders. In a mobile app, open checkout_url in the system browser or an in-app browser (not a bare webview), and use a return_url your app can catch (universal link).

Before selling: tell the truth

Two free checks avoid selling a useless eSIM:

  • GET /coverage/{destination} (any key): which main French mobile plans already include the destination.
  • GET /compatibility?device=iPhone 13 mini (any key): whether the phone takes an eSIM.

Net price instead of commission

If you sell the eSIM inside your own package or invoice it yourself, order with "kind": "net" from your server: it is paid from your prepaid credit, delivered at once (or deliver_at), and you get esim.page_url and the install links to show in your own pages. See Partner API.