TitanSim / Developers

Webhooks and events

Receive signed lifecycle events or poll the event feed.

Event types

Register up to three HTTPS webhook endpoints per mode. A create response shows its signing secret once. Subscribe to selected event types or use the default selection. Each event carries the resource as the API returns it: order.completed carries the Order, esim.* events carry the Esim (esim.usage adds threshold_percent 50 or 80; esim.depleted means 100% used), topup.completed carries the Topup including balance_after_usd, and balance.low carries balance_usd and low_balance_threshold_usd.

  • order.completed
  • esim.activated
  • esim.usage
  • esim.depleted
  • esim.expiring
  • esim.expired
  • topup.completed
  • balance.low
  • webhook.test

Verify signatures

Use the raw request body. TitanSim-Signature has t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>. Reject timestamps outside five minutes. Compare digests in constant time.

Node.js verifier
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyTitanSimWebhook(secret, rawBody, header) {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
  if (!match) return false;
  const timestamp = Number(match[1]);
  if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8').digest();
  return timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}

Python verifier

Use the same raw bytes before JSON parsing.

Python verifier
import hmac
import hashlib
import time
import re

def verify_titansim_webhook(secret: str, raw_body: bytes, header: str) -> bool:
    match = re.fullmatch(r't=(\d+),v1=([0-9a-f]{64})', header or '')
    if not match:
        return False
    timestamp = int(match.group(1))
    if abs(time.time() - timestamp) > 300:
        return False
    signed = str(timestamp).encode() + b'.' + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, match.group(2))

Delivery and polling

Any 2xx response acknowledges delivery. Retries occur after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours, then the delivery is exhausted. GET /events?cursor=&type= is a polling alternative, oldest first from the cursor, with 30-day retention.

Timing: order.completed and topup.completed are sent within seconds. Activation, usage, depletion and expiry events follow carrier reporting and our sync cadence — expect them within about 15 minutes of the change being observed; esim.expiring arrives between 24 and 20 hours before expires_at.

  • TitanSim-Event-Id identifies an event.
  • TitanSim-Event-Type names its type.
  • POST /webhook-endpoints/{id}/test sends webhook.test.

Cookies & privacy

We use analytics and marketing cookies (Google Analytics, LinkedIn Insight Tag) to understand usage and measure our ad campaigns. Privacy Policy