eimHub developers

Webhooks

Instead of polling, register a webhook endpoint (POST /api/v1/webhook-endpoints, or in the console) and receive events as signed HTTPS POST requests. An operation create may also name its own callback_url, which overrides the endpoints for that operation's events.

Events

Event When
operation.completed an operation ended SEND_SUCCESS
operation.failed an operation ended SEND_FAILURE
operation.expired an operation expired before delivery
operation.cancelled an operation was cancelled, superseded by a retry, or its eUICC deleted
operation.awaiting_confirmation an operation waits for the card's notification
operation.delivered the card collected an operation (opt-in: endpoints that list it only)
euicc.registered a card's certificates were stored for the first time
euicc.association_changed, euicc.association_broken, euicc.last_eim_deleted changes of a card's eIM association
euicc.deleted, euicc.state_change_reported a card was deleted / reported a state change
import.finished an import job finished
notification.received, notification.relay_failed a GSMA notification from a card was stored / could not be relayed
webhook.test the endpoint's test event

Payload

{
  "id": "0192…", "type": "operation.completed", "created_at": "2026-10-24T08:00:00.000Z",
  "tenant_id": "…", "sub_tenant_path": "acme.eu",
  "data": { "operation_id": "…", "eid": "89049032123451234512345678901234", "action": "ENABLE",
            "operation_status": "COMPLETED", "command_status": "SEND_SUCCESS", "outcome": [] },
  "links": { "self": "/api/v1/operations/…" }
}

Signature

Deliveries follow Standard Webhooks. Each carries three headers:

import base64, hashlib, hmac, time


def verify(secret: bytes, headers, body: bytes) -> bool:
    msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
    if abs(time.time() - int(ts)) > 300:
        return False  # older than 5 minutes: refuse
    mac = hmac.new(secret, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256).digest()
    expected = base64.b64encode(mac).decode()
    return any(
        hmac.compare_digest(sig.removeprefix("v1,"), expected)
        for sig in headers["webhook-signature"].split()
    )

Receiver checklist

  1. Verify webhook-signature with the endpoint secret over webhook-id.webhook-timestamp.body; reject timestamps older than 5 minutes.
  2. Respond 2xx within 10 s; do the real work asynchronously.
  3. De-duplicate on webhook-id.
  4. Do not infer ordering from arrival; read operation_id + command_status (+ attempt) from data.
  5. Fetch the full resource from links.self when the summary is not enough; the payload is a snapshot at emission time.
  6. Rotate the secret from the UI and keep accepting both signatures for 24 h.

Retries

A delivery that does not answer 2xx within 10 seconds is retried after 1 min, 5 min, 30 min, 2 h and 12 h — six attempts in all — when it failed with a 5xx, 408 or 429, a connection error or a timeout. Any other 4xx is not retried. A delivery that runs out of attempts becomes a dead letter, kept for 30 days and replayable from the console or with POST /api/v1/webhook-deliveries/{delivery_id}:replay; a replay keeps the original webhook-id.