
# Implement Evergreen Roaming

Evergreen Roaming gives every user a permanent eSIM they top up with data packages on demand. Your backend provisions the user once, and Gigs Connect handles installation, checkout, account management, and support from then on.

## Prerequisites

Before you begin, you need:

- A Gigs project with Gigs Connect and Gigs Billing enabled
- Your Gigs project ID, used below as `GIGS_PROJECT`
- An API bearer token scoped to that project, used below as `GIGS_TOKEN`
- A plan: an eSIM plan on the `p15` provider, with zero allowance and zero price (`pln_` prefix)
- At least one [add-on][add-ons] (`add_` prefix) the subscription can be topped up with. In this guide, add-ons are referred to as _data packages_, the name users see in Connect
- Optional: a deep-link scheme registered in your app, for example `your-app://connect-return`

Gigs configures the plan and the add-ons during project setup. Both IDs are in the [Gigs Dashboard](https://dashboard.gigs.com).

<Note type="info">

Add-on IDs are static, but changing an add-on's price creates a new add-on ID. Read the catalogue at runtime instead of hard-coding add-on IDs.

</Note>

### Authenticate your requests

Send the API bearer token in the `Authorization` header on every request. The token grants full access to the project, so hold it as a secret on your backend and never embed it in a mobile app.

<CodeGroup title="Authorization header" scroll={false}>

```bash
Authorization: Bearer ${GIGS_TOKEN}
```

</CodeGroup>

A missing or invalid token returns `401 Unauthorized`. [Authentication][authentication] covers creating and rotating keys.

Every endpoint in this guide is relative to the project base URL:

<CodeGroup title="Base URL" scroll={false}>

```bash
https://api.gigs.com/projects/${GIGS_PROJECT}
```

</CodeGroup>

## Overview

Provisioning runs server to server and happens once per user: a user, a permanent subscription on the zero-allowance plan, and a free data add-on. Everything the user does after that runs through Gigs Connect, using [Connect Sessions][about-connect-sessions]. [Creating Connect Sessions][creating-connect-sessions] covers the request fields and [best practices][connect-sessions-best-practices], and [Link back to your application][link-back] covers the callback. Webhooks are optional and tell you about changes the user didn't trigger, such as a yearly renewal or a usage threshold.

```mermaid
sequenceDiagram
    participant App as Your app
    participant You as Your backend
    participant API as Gigs API
    participant Connect as Gigs Connect
    App->>You: User opens the eSIM section
    You->>API: POST /users
    You->>API: POST /subscriptions
    You->>API: POST /subscriptionAddons
    API-->>You: com.gigs.subscription.activated
    You->>API: POST /connectSessions
    API-->>You: Single-use session URL
    App->>Connect: Open the URL in<br/>an in-app browser
    Connect-->>App: Redirect to callbackUrl
```

### What you build and what Gigs provides

| Area | You build | Gigs provides |
| ---- | --------- | ------------- |
| Create a user (`POST /users`) | Required. Server-to-server call | API endpoint |
| Create the subscription (`POST /subscriptions`) | Required. Server-to-server call | API endpoint and automatic eSIM provisioning |
| Grant a free data add-on (`POST /subscriptionAddons`) | Required. Server-to-server call | API endpoint and a pre-configured free add-on |
| eSIM install screen | Optional. A native screen showing the QR code and platform deep links | Connect Session with `viewEsimInstallation`, plus raw credentials, a hosted QR image, and deep-link URLs if you build your own |
| Re-install after a device change | Nothing | Connect Session with `viewEsimInstallation` intent |
| Data usage, package store, checkout, and support | Nothing | Connect Session with `evergreenRoaming` intent |
| Payment method and card updates | Nothing | Gigs Connect, backed by Gigs Billing |
| Webhook handler | Optional | Events delivered by Svix |

## Provision the user and the eSIM

### 1. Create a user

Create a Gigs user the first time a customer opens the eSIM section in your app. `email` is the only required field.

You may use a placeholder address from your internal customer ID. The customer isn't asked for a real email at this point, and you can enrich the record later.

<CodeGroup title="Creating a user">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/users" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "email": "user-4821@esim.example.com"
  }'
```

</CodeGroup>

| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| `email` | string | Required | A syntactically valid address, unique within the project |
| `fullName` | string | Optional | Can be added later through the `updateUserFullName` intent |
| `preferredLocale` | string | Optional | For example `fr-FR`. Sets the default language of Connect screens |

<CodeGroup title="A successful response after creating a user">

```json
{
  "object": "user",
  "id": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d",
  "email": "user-4821@esim.example.com",
  "emailVerified": false,
  "fullName": null,
  "preferredLocale": "en-US",
  "status": "active",
  "createdAt": "2026-05-05T09:31:39Z"
}
```

</CodeGroup>

Store `id`, the Gigs user ID, against your internal user record. Every later call needs it.

A successful create always returns a `status` of `active`.

### 2. Add the user's country of residence

Gigs is the Merchant of Record for data-package purchases and applies the correct VAT rate per jurisdiction. Send the user's country so checkout doesn't have to ask for it.

Skip this call if you don't hold the user's country of residence. Connect then asks the user to confirm it during checkout, which adds a step to the funnel.

<CodeGroup title="Adding the user's country of residence">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/users/${USER_ID}/addresses" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "country": "ES",
    "line1": "",
    "city": ""
  }'
```

</CodeGroup>

Note that only `country` is needed, so `line1` and `city` can be empty strings.

<CodeGroup title="A successful response after adding an address">

```json
{
  "object": "userAddress",
  "id": "adr_0SNlurA049MEWV5ELDmnaqVXgTFT",
  "country": "ES",
  "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d",
  "createdAt": "2026-05-05T09:31:52Z"
}
```

</CodeGroup>

An invalid or missing `country` returns `422 Unprocessable Entity` with a `details` field describing the error. Use an ISO 3166-1 alpha-2 code, for example `ES`, `MX`, `US`, or `BR`.

### 3. Create the evergreen subscription

The subscription is the permanent container for the user's eSIM, data packages, and usage records. Create one per user. It's never re-created, which is what makes the flow evergreen: buying a data package adds an add-on on top of the same subscription.

<Note type="warning">

For evergreen roaming, create the subscription on the zero-allowance plan, not on a plan that carries data. The free allowance arrives separately as an add-on in step 4.

</Note>

Replace the `plan` value with the zero-allowance plan ID from your project setup.

<CodeGroup title="Creating the evergreen subscription">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/subscriptions" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "plan": "pln_0SNlurA049MEWV6QrTbnKyC2Lp8x",
    "sim": "auto",
    "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d"
  }'
```

</CodeGroup>

| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| `plan` | string | Required | The static plan ID from project setup |
| `user` | string | Required | The `usr_` ID from step 1 |
| `sim` | string | Optional | Set to `"auto"` so Gigs provisions a fresh eSIM |

<CodeGroup title="A successful response after creating a subscription (abbreviated)">

```json
{
  "object": "subscription",
  "id": "sub_0U1WP6391FmqGW0gb3T3TGbsb8rW",
  "plan": { "id": "pln_0SNlurA049MEWV6QrTbnKyC2Lp8x", "...": "..." },
  "user": { "id": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d", "...": "..." },
  "sim": null,
  "status": "pending",
  "currentPeriod": { "...": "..." },
  "createdAt": "2026-05-05T09:32:13Z",
  "activatedAt": null,
  "firstUsageAt": null
}
```

</CodeGroup>

Store `id`, the subscription ID. Every Connect Session from step 5 on needs it.

The [subscription][subscription-schema] returns as `pending` and activates once the network provisions the eSIM, typically in 5 to 10 seconds. You don't need to wait for `active` before granting the add-on in step 4. You do need it before step 5, so subscribe to `com.gigs.subscription.activated` or check the status under `GET /subscriptions/{id}`.

### 4. Grant the free data add-on

The plan carries zero allowance, so on its own it provisions an eSIM with no usable data. If you want to provision free data to the user, it will arrive as a subscription add-on: a per-user instance (`sad_`) of the free add-on (`add_`) that Gigs configures at project setup.

Replace the `addon` value with the free add-on ID from your project setup.

<CodeGroup title="Granting the free data add-on">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/subscriptionAddons" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "addon": "add_0SNlurA049MEWV7StUcoLzD3Mq9y",
    "subscription": "sub_0U1WP6391FmqGW0gb3T3TGbsb8rW",
    "billing": {
      "paymentBehavior": "none"
    }
  }'
```

</CodeGroup>

| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| `addon` | string | Required | The static add-on product ID for the free package, from project setup |
| `subscription` | string | Required | The `sub_` ID from step 3 |
| `billing.paymentBehavior` | enum | Optional | Gigs Billing only. Use `none` for the free grant so the zero-cost invoice settles automatically. |

<CodeGroup title="A successful response after granting the add-on (abbreviated)">

```json
{
  "object": "subscriptionAddon",
  "id": "sad_0U2A1bQ7c3FmqGW0kLpZ9rstUv4m",
  "addon": { "id": "add_0SNlurA049MEWV7StUcoLzD3Mq9y", "...": "..." },
  "subscription": "sub_0U1WP6391FmqGW0gb3T3TGbsb8rW",
  "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d",
  "status": "pending",
  "billing": { "paymentBehavior": "none", "...": "..." },
  "currentPeriod": null,
  "activatedAt": null,
  "canceledAt": null,
  "endedAt": null,
  "createdAt": "2026-05-05T09:32:20Z"
}
```

</CodeGroup>

Store `id`, the subscription add-on ID. Subscribe to `com.gigs.subscriptionAddon.activated`, or query `GET /subscriptionAddons/{id}`, to detect when the allowance is usable. Once the add-on is `active` it appears in the subscription's `usageBalances`.

## Hand the user to Connect

### 5. Show the eSIM install screen

Once the subscription is `active`, show the install experience. Launch a Connect Session with the [`viewEsimInstallation`][intent-viewesiminstallation] intent and Connect renders the QR code, platform deep links, and step-by-step instructions. The same intent is the re-install path after a device change or a lost phone, so launch it again whenever the user needs to reinstall their eSIM. This requires a [SIM exchange][change-existing-subscriptions] triggered through the API first.

<CodeGroup title="Creating a Connect Session with the viewEsimInstallation intent">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/connectSessions" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "callbackUrl": "your-app://esim-install-done",
    "intent": {
      "type": "viewEsimInstallation",
      "viewEsimInstallation": {
        "subscription": "sub_0U1WP6391FmqGW0gb3T3TGbsb8rW"
      }
    },
    "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d"
  }'
```

</CodeGroup>

<CodeGroup title="A successful response after creating a Connect Session">

```json
{
  "object": "connectSession",
  "id": "csn_0U2UMLEo0Z7P8Y37BOdNohpMnq0Y",
  "url": "https://connect.gigs.com/checkout/entry?session=csn_0U2UMLEo0Z7P8Y37BOdNohpMnq0Y&token=...",
  "callbackUrl": "your-app://esim-install-done",
  "intent": { "...": "..." },
  "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d"
}
```

</CodeGroup>

To render the install screen natively instead, read the SIM credentials and build your own. The activated subscription's `sim` object carries the SIM ID.

<CodeGroup title="Retrieving SIM credentials">

```bash
curl --request "GET" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/sims/${SIM_ID}/credentials" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}"
```

</CodeGroup>

<CodeGroup title="A successful response with SIM credentials">

```json
{
  "object": "simCredentials",
  "sim": "sim_0U1WQria1FmqGW0nNQDxZqvrX7Nr",
  "activationCode": "LPA:1$smdp.gigs.com$TEST-OUKOP-8JW6Y8B",
  "qrCodeUrl": "https://files.gigs.com/qrcode/dev/6tu8ZcW5IBi.png",
  "iosInstallUrl": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$smdp.gigs.com$TEST-OUKOP-8JW6Y8B",
  "androidInstallUrl": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$smdp.gigs.com$TEST-OUKOP-8JW6Y8B"
}
```

</CodeGroup>

Lead with `iosInstallUrl` or `androidInstallUrl` for a one-tap install, and fall back to `qrCodeUrl` or `activationCode`. [Create a subscription][create-a-subscription] describes each field and its device requirements.

<Note type="danger">

Treat SIM credentials like a password. An eSIM activates only once, and `qrCodeUrl` is unauthenticated by design so that any device can render it. Never log it, email it, or store it beyond the user session.

</Note>

### 6. Open the Evergreen Roaming session

Every top-up and account action after installation runs through a Connect Session with the `evergreenRoaming` intent. That one intent composes the data-usage overview, the package store, checkout, and support.

<CodeGroup title="Creating a Connect Session with the evergreenRoaming intent">

```bash
curl --request "POST" \
  --url "https://api.gigs.com/projects/${GIGS_PROJECT}/connectSessions" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer ${GIGS_TOKEN}" \
  --header "Content-Type: application/json" \
  --data '{
    "callbackUrl": "your-app://topup-return",
    "intent": {
      "type": "evergreenRoaming",
      "evergreenRoaming": {
        "subscription": "sub_0U1WP6391FmqGW0gb3T3TGbsb8rW",
        "entryPoint": "manageDataPackages"
      }
    },
    "user": "usr_0SNlurA049MEWV4OpCwsNyC9Kn2d"
  }'
```

</CodeGroup>

| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| `intent.evergreenRoaming.subscription` | string | Required | The user's `sub_` ID |
| `intent.evergreenRoaming.entryPoint` | enum | Optional | `manageDataPackages` (default), `browseDataPackages`, or `null` |

Set `entryPoint` to `browseDataPackages` to open the store directly. The composed intent is the recommended path because it lets users pick their own data packages. You can also launch `manageDataPackages`, `browseDataPackages`, [`checkoutAddon`][intent-checkoutaddon], and [`requestSupport`][intent-requestsupport] as standalone intents to compose the flow yourself.

When the session ends, Connect redirects to `callbackUrl` with `session_id` and `status` appended:

| Scenario | Your `callbackUrl` | Connect redirects the user to |
| -------- | ------------------ | ----------------------------- |
| Link back to your site | `https://example.com/topup-return` | `https://example.com/topup-return?session_id=csn_123&status=success` |
| Deep link into your app | `your-app://topup-return` | `your-app://topup-return?session_id=csn_123&status=success` |

Read the `status` parameter to decide what your app shows next:

| `status` | What happened | What your app should do |
| -------- | ------------- | ----------------------- |
| `success` | The user completed the action | Refresh local state, such as the usage bar, and show a confirmation screen |
| `user_aborted` | The user closed or cancelled Connect | Return the user to your eSIM home screen |
| `auth_expired` | The session URL was no longer valid when the user reached it | Offer a retry that creates a fresh Connect Session |
| `bad_configuration` | The session attempted a flow the project doesn't allow | Show an error and contact [support@gigs.com][gigs-support] |

For native apps, use a deep link that your app is configured to handle. [Creating Connect Sessions][creating-connect-sessions] covers using Connect Sessions in native apps.

## Subscribe to webhooks

Webhooks are optional. You can launch without them by, for example, checking `GET /subscriptions/{id}` and trusting Connect's `callbackUrl`. They earn their place when you want to react to events the user didn't trigger, such as a yearly renewal or a usage threshold.

Configure one HTTPS endpoint in the [Dashboard](https://dashboard.gigs.com/developers). [Events and webhooks][events-webhooks] covers endpoint setup, signature verification, retries, and event ordering.

## Where to go from here

- [Gigs API reference][api-introduction] for every endpoint used above
- [Creating Connect Sessions][creating-connect-sessions] for session fields, appearance, and native-app rendering
- [About Connect Sessions][about-connect-sessions] for every available intent
- [Add-ons][add-ons] for the add-on catalogue, coverage profiles, and the full subscription add-on lifecycle
- [Svix manual verification guide](https://docs.svix.com/receiving/verifying-payloads/how-manual) for webhook signatures
- Reach out to [support@gigs.com][gigs-support] for assistance

[authentication]: /api/authentication
[api-introduction]: /api/introduction
[gigs-support]: mailto:support@gigs.com
[creating-connect-sessions]: /docs/connect/connect-sessions/creating-connect-sessions
[about-connect-sessions]: /docs/connect/connect-sessions/about
[link-back]: /docs/connect/connect-sessions/link-back
[connect-sessions-best-practices]: /docs/connect/connect-sessions/creating-connect-sessions#best-practices
[intent-viewesiminstallation]: /docs/connect/connect-sessions/intent-viewesiminstallation
[intent-checkoutaddon]: /docs/connect/connect-sessions/intent-checkoutaddon
[intent-requestsupport]: /docs/connect/connect-sessions/intent-requestsupport
[create-a-subscription]: /docs/create-a-subscription
[change-existing-subscriptions]: /docs/core/change-existing-subscriptions
[add-ons]: /docs/core/add-ons
[events-webhooks]: /docs/core/events/events-webhooks
[subscription-schema]: /core/schemas/subscription
