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:
POST /quoteswith destination and dates: take the option withrecommended: true.POST /orderswith that plan, the traveller's email, yourreferenceandreturn_url. The answer hascheckout_url.- Put
checkout_urlin your email, SMS or button. Or passnotify_client: trueand 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.