DEV Community

Wae Luxe
Wae Luxe

Posted on

How I Handled eSIM Activation Failures Across 200+ Countries (And What I Learned)

Why Building an eSIM Platform Sounds Simple (But Isn't)

When I started building iWanteSIM, I naively assumed the hardest part would be negotiating carrier deals and getting coverage in 200+ countries. Turns out, that was the easy part. The real nightmare started the moment we tried to activate eSIM profiles at scale.

eSIM activation is a deceptively complex pipeline. On the surface, it looks straightforward: user buys a plan → carrier provisions an eSIM profile → profile downloads to device → you're connected. But when you're orchestrating this across 200+ countries, each with its own carrier API, timeout behavior, error format, and network quirks, the edge cases multiply faster than you can handle them.

This is the story of how I failed, learned, and eventually built a system that could gracefully handle activation failures across the entire globe.

The First 1,000 Activations Were a Lie

Our initial launch covered 50 countries. Everything worked beautifully in testing. We tested on iPhone 14s, Samsung Galaxy S23s, Google Pixels — the usual suspects. Success rate: 98%. We patted ourselves on the back and expanded to 100 countries.

Then came Indonesia, Nigeria, and Peru.

Our activation success rate plummeted to 73% in certain regions. Users would purchase, wait, and get nothing. Support tickets piled up. We'd dig into logs and find cryptic carrier errors reading like:

GSMA_RSP_ERROR: 3.2.11 — Operation failed
HTTP 500 — Internal Server Error
Timeout after 30s: no SM-DP+ response

Enter fullscreen mode Exit fullscreen mode

These weren't one-off failures. They were patterns. And they were different in almost every country.

Technical Difficulties Please Stand By

Mapping the Failure Landscape

After three sleepless weeks of log analysis, I categorized the failures into five buckets:

1. Carrier API Timeouts

Some carriers' SM-DP+ servers (the servers that prepare and deliver eSIM profiles) would timeout after exactly 30 seconds. Others would hang indefinitely. A few would respond instantly to our test calls but timeout under real load.

2. Non-Standard Error Codes

The GSMA RSP specification defines a standard set of error codes for eSIM provisioning. You'd think carriers would use them. They don't. We saw HTTP 200 responses with error payloads, HTTP 500s with success payloads, and one carrier that returned HTTP 418 (I'm a teapot) when their eSIM queue was full.

3. Network-Level Failures

In some countries, the local cellular network infrastructure would interfere with the eSIM profile download. Users on Carrier A couldn't activate an eSIM for Carrier B because the local network intercepted or throttled the OTA (over-the-air) profile download.

4. Device Firmware Incompatibilities

Older Android devices with outdated eSIM firmware would reject profiles that newer devices handled fine. We discovered that some Chinese OEM phones had non-standard eSIM implementations that failed on GSMA-compliant SM-DP+ endpoints.

5. Regional Rate Limiting

Several carriers had undocumented rate limits — 5 activations per minute, 100 per hour, etc. Exceed them and you'd get silently blocked for 24 hours. No error message. Just dead silence.

Building the Retry Logic That Saved Us

The first version of our retry logic was embarrassingly naive:

async function activateSIM(profileId) {
  try {
    return await carrierAPI.provision(profileId);
  } catch (err) {
    // Try again?
    return await carrierAPI.provision(profileId);
  }
}

Enter fullscreen mode Exit fullscreen mode

This worked exactly as well as you'd expect — which is to say, not at all. Same call, same timeout, same failure. We needed exponential backoff with carrier-aware strategies.

The Real Retry System

After multiple iterations, here's what actually worked:

class ActivationRetryHandler {
  constructor(carrierProfile) {
    this.carrier = carrierProfile;
    this.maxRetries = carrierProfile.maxRetries || 3;
    this.backoff = carrierProfile.backoffStrategy || 'exponential';
    this.timeoutMultipliers = {
      'fast': [5, 15, 45],       // seconds
      'normal': [10, 30, 90],
      'slow': [30, 90, 270]
    };
    this.circuitBreaker = new CircuitBreaker({
      threshold: carrierProfile.failureThreshold || 5,
      resetTimeout: 300000  // 5 minutes
    });
  }

  async activateWithRetry(activationRequest) {
    if (this.circuitBreaker.isOpen()) {
      return { status: 'circuit_open', retryAfter: this.circuitBreaker.retryAfter };
    }

    const timings = this.timeoutMultipliers[this.carrier.speedClass || 'normal'];

    for (let attempt = 0; attempt  c.count > alertingThreshold)
    .map(([sig, data]) => ({ signature: sig, ...data }));
}

Enter fullscreen mode Exit fullscreen mode

Space launch

Launching to 200+ Countries (For Real This Time)

After months of iterating on the activation pipeline, we rolled out to 200+ countries with a system that could handle failures gracefully. The result? 94.7% first-attempt activation success rate, and 98.2% within three retries.

We wrote extensively about how eSIM works and how to activate it on our What is an eSIM guide. If you're curious about the specifics of our activation flow, our eSIM Activation Guide walks through the process step by step.

Key Takeaways

If you're building a global service that depends on third-party APIs across multiple regions, here's what I wish I'd known from day one:

  1. Test in production, but test carefully. Simulated environments never replicate real-world carrier behavior. Build monitoring that detects anomalies in real-time.
  2. Treat every carrier as a unique API. Even if they all adhere to the same GSMA standard, their implementations will differ. Build carrier-specific adapters from day one.
  3. Circuit breakers are non-negotiable. When a carrier's API goes down, your retries should not make it worse. Back off, queue, and retry with grace.
  4. Monitor at the carrier-region-device level. A failure that affects only iPhone users on one carrier in Brazil tells a different story than a global outage. Granular data saves hours of debugging.
  5. Document every quirk. That carrier that returns HTTP 418? Document it. In six months when someone refactors the integration, they'll need to know.

What About You?

Are you building something that depends on global third-party APIs? What's the weirdest error response you've ever seen from a production system? I'd love to hear your war stories in the comments.

Top comments (0)