docs

Event catalog

Every async state change in heliumOS emits a webhook event. This catalog enumerates the event types you can subscribe to; payload shapes are documented in the API reference under the Events tag. The authoritative, always-current list is served at GET /v1/reference/event_types, and webhook endpoint enabled_events is validated against it.

Shape

Every event carries:

{
  "id": "evt_...",
  "type": "sim.activated",
  "created_at": "2026-04-24T15:00:00Z",
  "livemode": false,
  "data": { "object": { ... } },
  "idempotency_key": "..."
}

Subscriber events

TypeFires when
subscriber.createdA subscriber record is created, whether through POST /v1/subscribers or the migration import (import-originated events carry data.imported: true)
subscriber.lockedA subscriber's SIM-swap / port-out protection lock is turned on (locked_status becomes user_initiated_lock)
subscriber.unlockedA subscriber's SIM-swap / port-out protection lock is turned off (locked_status becomes unlocked)

Subscriber event payloads are always PII-redacted: email, phone, and date_of_birth are null and the object carries pii_redacted: true. Read the subscriber back through GET /v1/subscribers/{id} (subject to your role's PII permissions) when you need the contact fields.

Subscription events

TypeFires when
subscription.createdA subscription record is created, whether through POST /v1/subscriptions (solo or group) or the migration import (import-originated events carry data.imported: true)
subscription.updatedA status or pending-plan-change reconciliation with no dedicated event below, such as a scheduled downgrade being set or cleared
subscription.plan_switchedA plan change applies (an upgrade immediately, a scheduled downgrade at renewal)
subscription.pausedBilling is put on hold — moves to paused, and a void collection pause appears on the object. Service is not interrupted; the SIMs keep working
subscription.resumedBilling restarts — moves from paused back to active and the collection pause is cleared
subscription.cancelledMoves to cancelled
subscription.terminatedMoves to terminated
subscription.past_duePayment is overdue and the subscription moves to past_due
subscription.unpaidPayment retries are exhausted and collection has stopped
subscription.recoveredA past_due subscription recovers to active after a successful payment
subscription.addon_addedAn add-on is attached to the subscription
subscription.addon_removedAn add-on is removed from the subscription
subscription.addon_expiredA time-bounded add-on reaches the end of its term

SIM events

The eSIM install code rides sim.provisioned, sim.activated and sim.swapped — but only for an endpoint that subscribes to them on purpose. The activation_code (LPA) string installs the subscriber's profile onto whatever device scans it, so it is delivered only where enabled_events names the type directly (sim.*, or the exact type). An endpoint left on the default catch-all * still receives every one of these events; the activation_code field is simply absent from the body.

Building an eSIM flow? Subscribe with sim.* rather than *. Wiring up an analytics sink or a general-purpose connector? * is the right choice, and it keeps install credentials out of it.

TypeFires when
sim.provisionedA SIM record is created and provisioned
sim.importedAn existing SIM is imported (migration)
sim.activation_startedActivation is requested from the carrier
sim.activatedCarrier confirms activation
sim.activation_failedCarrier rejects activation
sim.activation_resolution_requiredActivation stalled and needs operator resolution
sim.suspendedMoves to suspended
sim.restoredMoves from suspended back to active
sim.deactivatedTerminal deactivation
sim.swappedA new SIM takes over the MSISDN
sim.reassignedThe SIM's placement moves — a different subscription, owner, or holder line (an explicit reassign, or a leftover SIM reused by a re-subscribe). The object carries the new placement (subscriber_id = the payer, holder_subscriber_id = the member who uses the line, subscription_id, subscription_line_id, external_subscriber_id); previous_attributes carries the old subscription_id, subscriber_id, subscription_line_id, and subscription_status. Mirroring the end user? Read holder_subscriber_id, not subscriber_id — the latter is the payer here but the link target on sim.linked. Re-pointing a mirrored subscription? Only when previous_attributes.subscription_status is cancelled.
sim.products_updatedThe SIM's provisioned products (SLOs) change
sim.msisdn_changedThe SIM's MSISDN changes
sim.boostedA data boost is applied
sim.boost_expiredA data boost reaches the end of its window
sim.throttledThe SIM is throttled after crossing a usage cap
sim.throttle_clearedThrottling is lifted (cycle rollover or manual clear)
sim.port_out_pin_generatedA port-out PIN is generated for the SIM
sim.port_out_pin_clearedThe SIM's port-out PIN is cleared

Port events

TypeFires when
port.createdA port-in request record is created
port.submittedThe port-in request is accepted by heliumOS
port.in_progressThe carrier has the port-in in flight
port.resolution_requiredThe port-in stalled and needs operator or subscriber input. status_reason carries the carrier's own message (e.g. 6B-T-Mobile Number Transfer PIN is required or incorrect) and status_reason_code its message code (6B) — branch on the code, show the text
port.resolution_expiringA stalled port-in is nearing the carrier's deadline (~30 days from submission) after which the transfer is cancelled and the line switched off. Fires once per submission, around day 21. Carries the same status_reason / status_reason_code, plus submitted_at and days_since_submitted
port.completedThe number transfer completed
port.failedThe port-in failed
port.cancelledThe port-in was cancelled
port.out_validation_requestedThe gaining carrier requested port-out validation
port.out_completedA port-out finalized and the line left heliumOS

Usage events

TypeFires when
usage.data_threshold_crossedA configured data-usage threshold is crossed
usage.voice_threshold_crossedA configured voice-usage threshold is crossed
usage.sms_threshold_crossedA configured SMS-usage threshold is crossed
usage.tier_changedThe SIM moves between throttle tiers
usage.cycle_rolled_overA billing cycle rolls over and counters reset

Billing events

TypeFires when
invoice.upcomingA pre-renewal notice ahead of the next billing cycle (a preview invoice object, status: "upcoming", no id yet). Sandbox environments emit it a few days before current_period_end; environments on a live Stripe connection relay Stripe's own invoice.upcoming, which must be enabled on the Stripe webhook endpoint
invoice.createdAn invoice is created
invoice.finalizedAn invoice is finalized and ready to collect
invoice.paidAn invoice is paid
invoice.payment_failedA payment attempt on the invoice fails
invoice.voidedAn invoice is voided
payment.succeededA payment succeeds
payment.failedA payment fails
payment.refundedA payment is refunded
payment_method.attachedA payment method is attached to a subscriber
payment_method.updatedA payment method becomes the subscriber's default
payment_method.detachedA payment method is detached from a subscriber

pSIM order events

TypeFires when
psim_order.paidA paid pSIM order is placed (the card is charged)
psim_order.createdA free pSIM order is placed (no charge)
psim_order.refundedA pSIM order's charge is refunded (or a paid order is canceled)
psim_order.canceledA free pSIM order is canceled

The recipient email and shipping_address are redacted in the event store and injected into the outbound webhook body at delivery time, so your fulfilment backend receives a complete order.

CPNI step-up events

When a support agent needs to view a subscriber's PII or change their account, heliumOS issues a one-time verification code and asks you to deliver it to the subscriber over your own channel (SMS, app push, email). The subscriber reads it back to the agent, who enters it to unlock access for a window.

TypeFires when
cpni.verification.requestedAn agent started a step-up challenge, carries the one-time code
cpni.verification.succeededThe agent entered the correct code
cpni.verification.failedThe challenge locked out after too many attempts

cpni.verification.requested is the only event whose payload contains a secret: data.object.code. Deliver it to the subscriber and treat it like a password: don't log it, and don't store it. The code is short-lived (operator-configured, default 10 minutes) and is never returned by the GET /events API; it rides the signed webhook only.

{
  "type": "cpni.verification.requested",
  "data": {
    "object": {
      "object": "cpni_verification",
      "id": "cpniv_01HGZX...",
      "subscriber_id": "sbr_01HGZX...",
      "agent": { "user_id": "...", "email": "agent@operator.example" },
      "code": "482913",
      "expires_at": "2026-06-08T17:10:00Z",
      "grant_duration_seconds": 1800
    }
  }
}

Carrier connection events

TypeFires when
carrier_connection.verifiedA scheduled verify call succeeded
carrier_connection.auth_failedA verify call failed authentication
carrier_connection.credentials_expiredThe connection's carrier credentials expired

Event types are additive: new types may appear without a major version bump. Handlers should ignore unknown type values.