DEV Community

Daniel
Daniel

Posted on

One Install, Many Trips: Designing Reusable eSIM Profiles, Top-ups and Unused-Data Refunds

The first eSIM purchase in your app is the easy part. The interesting engineering starts on the second trip, when the customer already has a profile installed and just wants more data. In the last few weeks several financial apps have shipped travel eSIM with exactly this in mind: Wise in New Zealand announced plans with automatic refunds for unused data, and PostFinance in Switzerland and Erste Bank's George app in Austria both describe an eSIM you install once and reuse across trips. Whatever provider sits behind each of them, the product pattern is the same: one install, many trips.
This article walks through the data model and event flow behind that pattern: reusable profiles, top-ups driven by usage, refunds for unused data, and several lines under one account. It assumes you're working with an eSIM provider's API and don't run any network infrastructure yourself.

1. Separate the profile from the bundle

The most common early mistake is modelling "an eSIM" as one purchase. It isn't. There are two different things with different lifecycles:

  • Profile: the installed SIM on the customer's device, identified by its ICCID. It's installed once and can live for months or years.- Bundle (or package, or plan): a quantity of data for a region and a validity period, attached to a profile. A customer can buy many over time.A minimal schema looks like this:
  • accounts: your customer, the person who pays.- lines: one row per profile, with account_id, iccid, a display name ("Dana's phone"), install status and the provider's profile reference.- bundles: line_id, product code, region, data allowance in bytes, validity in days, purchased_at, activated_at, expires_at, status, and the provider's order reference.- usage_snapshots: bundle_id, bytes used, observed_at, source (webhook or poll).- ledger_entries: every charge, refund and credit, linked to a bundle.With this split, "buy more data" becomes "create a bundle on an existing line" and never triggers a new QR code. Before showing a second QR code to anyone, check whether the customer already has an installed line that supports the destination. ## 2. Decide when a bundle counts as started Providers differ on when the validity clock starts. Common options are: at purchase, at first network attach in the covered country, or at first data usage. Your app needs to know which applies to each product, because it drives three things: what you show the customer ("valid 7 days from first use"), when you can safely sell another bundle on the same line, and whether a refund is still possible. Store the rule per product, not as a global constant. Then compute bundle state from events rather than flags:
  • purchased: paid, not yet active.- active: provider reports activation or first usage.- depleted: usage reached the allowance.- expired: validity ended with data left over.- refunded or partially_refunded: money went back.If a line holds several bundles, also decide on ordering. Most providers consume the oldest active bundle first, but confirm it, because it changes how you display "data remaining". ## 3. Drive top-ups from usage events Top-ups convert best when they're offered at the moment of need. That means reacting to usage, not to a calendar. Two sources are typical:
  • Webhooks for thresholds (for example 80% and 100% used) and for activation and expiry.- Polling the usage endpoint as a fallback, because webhooks get delayed or dropped.Treat every incoming event as a snapshot, not a delta. Write it to usage_snapshots, make the handler idempotent on the provider's event id, and recompute the bundle state from the latest snapshot. Usage counters from networks can lag by minutes to hours, so never assume the number is real time; say "about" in the UI. Then define your triggers in one place:
  • At 80% used with more than a day of validity left, send an in-app notification with a one-tap top-up for the same region.- At 100% used, show the top-up as the first thing on the eSIM screen, using the stored payment method.- If a bundle expires with the customer still in the country (you can infer this from recent usage), offer a short extension rather than a full new plan.Keep the rule engine boring: a table of conditions and actions, evaluated after each state recompute, with a cooldown per line so nobody gets three notifications in an hour. ## 4. Refunds for unused data need reconciliation A refund-for-unused-data promise sounds simple and is mostly accounting. Before you offer one, answer these questions with your provider and your finance team:
  • What is "unused"? A bundle never activated is easy. A bundle that expired at 40% used is a policy choice: pro-rata, fixed tiers, or nothing.- Which number is authoritative? Your snapshots may lag. Refund on the provider's final usage figure after expiry, not on the last webhook you happened to receive.- When is it final? Wait a settlement window after expires_at (ask the provider how late usage records can arrive) before calculating.- What does it cost you? Check whether the provider credits you for unused data. If not, every refund comes out of your margin, so price accordingly.Implement it as a scheduled job that picks up expired bundles past the settlement window, fetches final usage, calculates the amount under the product's policy, and writes a ledger_entries row before calling the payment provider. The ledger row is the source of truth for support and reporting; the payment call can be retried. ## 5. Several lines under one account Families and small teams travel together, and one payer with several phones is a natural extension. The accounts/lines split already supports it. What you add is:
  • A way to send an install link to another person's device without exposing the payer's login.- Per-line notifications routed to the line holder, with billing notifications to the payer.- Optional spend caps per line, enforced in your top-up flow before you call the provider.Be explicit in the UI about which line a top-up is for. "Top up Dana's phone" prevents most of the support tickets this feature can create. ## 6. Plan for the support path When a reused profile doesn't connect on the second trip, the customer contacts you, not the network. Give your support team a single view per line: profile status, every bundle with its state and usage history, the last events received, and the ledger. Most tickets ("I bought data but it doesn't work") are answered by checking whether the new bundle is active, whether data roaming is on for that line, and whether the right line is selected for mobile data. ## Checklist
  • Profiles and bundles are separate entities.- Validity rules are stored per product.- Bundle state is computed from idempotent usage snapshots.- Top-up triggers live in one rule table with cooldowns.- Refunds run after a settlement window, from final provider usage, through a ledger.- Multi-line accounts route notifications and spend caps per line.## Where One-eSIM fits Disclosure: I work on One-eSIM, a B2B platform that handles the telecom side of this (connectivity supply, provisioning, top-ups, lifecycle events, support tooling and analytics) through an API or a white-label storefront. If you're adding reusable travel eSIM to a fintech, travel or mobility app, the travel, mobility and fintech page explains how that works. Happy to answer questions in the comments, including if you're building it all yourself.

Top comments (0)