Webhooks
How eSIM status and lifecycle events are surfaced - polling today, webhooks planned
Outbound client webhooks for eSIM are not yet wired. As with topups, voucher orders already fire client webhooks on terminal transitions, but the esim.* family of events has not yet shipped. Until they do, surface status changes by polling.
How to Track eSIM Status Today
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.
Planned Webhook Events
When eSIM outbound webhooks land, the envelope will match 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",
"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 | Notes |
|---|---|---|
esim.delivered | Order moved to DELIVERED and an activation code is available | Payload will not include the activation_code; fetch the order to retrieve it. |
esim.failed | Order moved to FAILED. Wallet auto-refunded. | Includes failure_code / failure_reason / is_user_fixable. |
esim.cancelled | Order cancelled administratively. Wallet refunded. | Always terminal. |
esim.installed | Customer's device downloaded the profile. | Lifecycle event, not an order event. ICCID identifies the eSIM. |
esim.activated | First data usage detected. | Lifecycle event. |
esim.depleted | Plan validity expired or data quota exhausted. | Lifecycle event. |
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
Even when esim.delivered ships, the activation_code will not be 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.