---
title: Put Terudo in your flow
description: 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).
---

# Put Terudo in your flow

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:

```js
// 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();
```

```html
<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

```jsx
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.

```tsx
// 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

```vue
<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:

```html
<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](https://terudo.com/developers/docs/api.md).
