Webhooks
How eSIM status and lifecycle events are surfaced - polling today, webhooks planned
eSIM order events (esim.delivered / esim.failed / esim.cancelled) fire at terminal status transitions, and lifecycle events (esim.installed / esim.activated / esim.depleted) fire as the eSIM is used. Each lifecycle event fires once, when the order genuinely transitions. Polling remains available as an alternative.
Tracking eSIM Status
1. Poll the order endpoint
GET /api/v1/esim/orders/:id refreshes installation status on every call for delivered-but-not-installed orders, and returns whatever's persisted otherwise.
# Initial fulfilment poll (10 minutes)
while true; do
resp=$(curl -s "$HOST/api/v1/esim/orders/$ORDER_ID" \
-H "Authorization: Bearer $TOKEN")
status=$(echo "$resp" | jq -r '.data.status')
case "$status" in
DELIVERED|FAILED|CANCELLED) break ;;
esac
sleep 5
done
# Post-delivery install poll (next 10 minutes)
while true; do
resp=$(curl -s "$HOST/api/v1/esim/orders/$ORDER_ID" \
-H "Authorization: Bearer $TOKEN")
installed=$(echo "$resp" | jq -r '.data.is_installed')
[ "$installed" = "true" ] && break
sleep 30
doneTwo poll phases:
| Phase | Watch for | Cadence |
|---|---|---|
| Fulfilment | status flips from PENDING to DELIVERED / FAILED / CANCELLED | 5–10s for the first minute, then 30s. An order that stays PENDING longer is Octopus Cards automatically retrying — it isn't stuck. |
| Install | is_installed flips to true; activation_status to ACTIVE | 30–60s for the first 10 minutes, then back off |
After 10 minutes uninstalled, stop the foreground install loop — the customer may install later, and you'd otherwise burn rate limits.
2. Attach a notification email
PATCH /api/v1/esim/orders/:id/notification-email attaches (or updates) an email address that is sent a templated receipt on terminal transitions — same pattern as topups and vouchers. Useful when the customer enters their email after placing the order, or when your UX can't render the eSIM details directly.
Webhook Events
The envelope matches the voucher webhook shape:
{
"id": "evt_esim_abc123",
"type": "esim.delivered",
"created_at": "2026-05-15T14:30:32Z",
"data": {
"id": 9302481,
"client_reference": "TRIP_TOKYO_OCT",
"status": "DELIVERED",
"activation_status": "NOT_INSTALLED",
"product_name": "Japan eSIM",
"country_code": "JPN",
"data_amount_gb": 1.0,
"validity_days": 7,
"amount": 4.275,
"currency": "USD",
"iccid": "8910300000123456789"
}
}Expected event types:
| Event | Trigger | Status |
|---|---|---|
esim.delivered | Order moved to DELIVERED and an activation code is available. Payload does not include the activation_code — fetch the order to retrieve it. | Live |
esim.failed | Order moved to FAILED. Wallet auto-refunded. Includes failure_code / failure_reason / is_user_fixable. | Live |
esim.cancelled | Order cancelled administratively. Wallet refunded. | Live |
esim.installed | Customer's device downloaded the profile (installed_at set, activation_status → ACTIVE). ICCID identifies the eSIM. | Live |
esim.activated | First data usage detected (activated_at set). | Live |
esim.depleted | Plan validity expired or data quota exhausted (activation_status → EXPIRED). | Live |
Lifecycle events carry the same data shape as order events; the activation_status field reflects the new state (ACTIVE for installed/activated, EXPIRED for depleted). The order status stays DELIVERED throughout — the event type and activation_status convey the lifecycle change.
In-flight workflow stages do not fire client webhooks — only terminal status transitions do. status: PENDING is the entire intermediate state; granular fulfilment stages are intentionally not surfaced.
Activation Code Handling in Webhooks
The activation_code is never included in the webhook payload. Webhook payloads are written to logs and may be retried; including a single-use credential there would risk exposure. Pattern:
- Receive
esim.delivered. - Pull
data.id(ordata.client_reference). - Call
GET /esim/orders/:idover your authenticated channel. - Render the LPA string / QR code immediately for the customer.
- Do not persist the activation code on your side beyond the install window.
This matches voucher webhooks, where order.delivered confirms delivery but the voucher codes themselves come from a follow-up GET /orders/:id.
For HMAC verification mechanics shared with outbound voucher webhooks, see Signature Verification.