Core concepts
Webhooks
Subscribe to activation and usage events.
Webhooks push lifecycle events to your backend so you don't have to poll.
Setting up an endpoint
Webhook endpoints are configured per client by Firsty. Sandbox accounts don't have self-serve webhook management yet. Get in touch with the HTTPS URL you want events delivered to and we'll wire it up with a signing secret.
Events
| Event family | Events |
|---|---|
| eSIM profile | esim.assigned, esim.downloaded, esim.installed, esim.enabled, esim.disabled, esim.deleted, esim.mccswitch |
| Packages | package.created, package.activated, package.usage, package.usage.threshold, package.throttled, package.exhausted, package.renewed, package.ended |
| Number port-in | portin.submitted, portin.approved, portin.rejected, portin.completed, portin.failed, portin.cancelled |
The ones most integrations care about first: esim.enabled (the user
genuinely has working data) and package.usage.threshold (time to offer a
top-up).
Verifying signatures
Deliveries are signed following the
Standard Webhooks spec: an HMAC-SHA256
over {webhook-id}.{webhook-timestamp}.{raw-request-body}, sent in the
webhook-signature header as v1,{base64}. Verify before trusting the
payload. An unverified webhook endpoint is an unauthenticated write endpoint.
Use the raw request body. Parsing to JSON and re-serialising changes key order and whitespace, and the signature will never match.
Delivery
Respond 2xx within 30 seconds and do the real work asynchronously. A 5xx,
a 408, a timeout or a connection failure is retried; nothing else is. So
don't answer 400 to an event you can't process yet, because that delivery
will not come back.
Delivery is attempted up to ten times over roughly 24 hours, backing off from 30 seconds to 8 hours. Events are retained for 7 days, and support can replay anything inside that window.
Delivery is at-least-once and ordering is best-effort, so the same event can
arrive twice, and package.activated can land before esim.enabled. Make the
handler idempotent by keying it on the webhook-id header, and treat each
event on its own rather than assuming a sequence.