Testing in sandbox
The sandbox runs the real platform wired to simulated backends for every third party — carrier, tax, payments. A dedicated Sandbox DX surface is available in the operator dashboard for driving state transitions that would otherwise require a real carrier: force-completing activations, seeding port scenarios, advancing the clock, and so on.
These controls live in the dashboard, not on the public API.
They're scoped to the dashboard's currently-selected environment
and only work when that environment is sandbox — there is no
programmatic test-mode surface for integration code, by design.
Driving state transitions
In the dashboard, open Sandbox → Triggers while your sandbox environment is selected. Pick a resource (SIM, port, subscription, carrier connection) and the trigger you want:
sim.force_activation_success— fast-forward a SIM frompending_activation→activeand firesim.activated.sim.force_activation_failure— forceactivation_failedand firesim.activation_failed.port.force_validation_failed— synthesize a port-in validation failure with a reason of your choice.port.force_completion— complete a port and activate the SIM.
The full trigger catalog is listed in the trigger panel and
documented inline per resource. Triggers are rejected outright in
live environments — they exist only for sandbox.
Rehearsing a failed payment
heliumOS charges the card itself for every bill it issues, and what it does next depends on what the card issuer said, not on the fact that the charge failed. There are three answers and three different behaviours, so seeing one of them is not seeing the others.
The simulated processor accepts every card, which is exactly why there is a lever. Open the subscription in the dashboard and use Test triggers → Force payment outcome. It arms the next charge on that subscription:
- Soft decline (insufficient funds) — the bill goes back in the queue and is presented again on the retry schedule's next rung, a day later. The subscription is marked past due.
- Hard decline (lost or stolen card) — nothing is retried on a timer. Re-presenting a card the issuer has refused outright recovers nothing, so the bill waits for a different card and collects as soon as one is attached.
- Expired card — the same wait, but the case the card networks fix for you: a corrected card number arriving from the issuer puts the bill straight back in the queue.
- Succeed — the next charge goes through. Use it to disarm a subscription you armed earlier, or to watch a delinquent account recover.
One arming is one charge. The outcome is spent by the attempt that uses it, so the attempt after that behaves normally — arm it again to refuse the next one too. That way a rehearsal can never leave a sandbox subscription that has stopped being collectable.
Each refusal carries the same decline code and message a live card
would (insufficient_funds, lost_card, expired_card), so the
invoice.payment_failed webhook your integration receives in sandbox
is shaped exactly like the one it will receive in production.
Combine this with the clock below to walk the whole retry schedule: arm a soft decline, advance a day, watch the retry, arm another.
Clock control
The sandbox clock runs at wall-clock time by default. Pin or advance it from Sandbox → Time travel in the dashboard:
- Advance by — jump the sandbox clock forward by N seconds / minutes / hours / days.
- Reset — restore the environment to wall-clock time.
Advancing the clock fires scheduled jobs immediately — a 30-day subscription renewal happens the moment you advance past the renewal mark.
The clock offset is per-environment, so advancing sandbox never affects live (live always reads wall-clock) and operators don't interfere with each other.
Scenario seeds
Bootstrap a full scenario (operator + subscriber + SIM + active subscription, or mid-port happy path, etc.) in one click from Sandbox → Seed scenarios in the dashboard. The available scenarios are listed in the panel alongside a short description of what each one creates.