Webhooks
Enregistrez l’URL via POST /v1/webhooks et modifiez-la via PUT /v1/webhooks/{id}. CHECK_HEALTH exige 2xx à la création. Les événements sont signés avec un secret distinct du secret API.
REST reste la source de vérité. Si un événement manque, GET /v1/orders?updated_since=… permet de reconstituer l’état. Prévoyez ce rapprochement.
webhook-events.fr.jsonCatalogue d’événements
| event_type | Déclenchement | Champs principaux |
|---|---|---|
order.completed | Commande confirmée, eSIM prêtes | order_idexternal_refitems[] (order_item_id, iccid) |
order.failed | Commande non terminée (échec de fourniture, etc.) | order_idexternal_refreason |
esim.delivered | Une eSIM a été fournie avec ses données d'installation Rattachez via `order_id` et `external_ref`. `lpa` est déchiffré juste avant POST, jamais stocké en clair dans les envois. Aucune URL d'image QR : générez depuis `lpa` ou lisez `qr_code_base64` avec `GET /v1/esims/{id}`. | order_item_idorder_idexternal_reficcidlpa |
esim.activated | L'eSIM a été installée et activée (smdp enabled/in_use) Utilisez `order_id` et `external_ref` (votre référence envoyée à l'achat) pour rattacher l'eSIM à votre commande. | order_item_idorder_idexternal_reficcidactivated_at |
esim.usage_threshold | Utilisation ayant atteint le seuil de 75 % / 90 % Rattachez l'eSIM via `order_id` et `external_ref`. Seul le plus haut seuil nouvellement franchi est émis (0→95 % émet seulement 90). `threshold` signifie au moins ce seuil dépassé. | order_item_idorder_idexternal_refthreshold (75/90)used_bytesdata_bytes |
esim.expiring | 3 jours / 1 jour avant expiration Utilisez `order_id` et `external_ref` (votre référence envoyée à l'achat) pour rattacher l'eSIM à votre commande. | order_item_idorder_idexternal_refdays_remaining (3/1)expires_at |
wallet.low_balance | Le portefeuille a atteint le seuil d'alerte de 25 % / 10 % `threshold` est un pourcentage (25/10), inclus seulement si connu, sinon absent (comparez `balance`). Jamais de montant VND brut dans `threshold`. | threshold (25/10)balancecurrency |
catalog.updated | Catalogue modifié : forfait ajouté, modifié, masqué, changement de prix de base ou rupture de stock Aucun prix inclus car les prix varient selon l'organisation. Consultez `GET /v1/catalog/packages`. Les changements d'une fenêtre courte (10 minutes par défaut) sont regroupés dans un seul webhook `changes[]`, une fois par forfait. Une synchronisation de centaines de forfaits n'envoie pas des centaines de webhooks. Modes test et réel. | changes[] (package_code, change: price|availability|package, occurred_at)change_count |
event_type: "ping"À chaque `POST /v1/webhooks` et `PUT /v1/webhooks/{id}`, contrôle de disponibilité avant sauvegarde.
Non configurable via `events` (y mettre `ping` renvoie `internal.validation_failed`) : tous les endpoints le reçoivent. Même enveloppe et signature réelle (`X-TKE-Webhook-Signature`, sans `-Old`) pour tester la vérification. Retournez 2xx pour un `event_type` inconnu : 4xx fait échouer la création ou modification avec `webhook.health_check_failed` (422), sans sauvegarde. `ping` n'apparaît pas dans `GET /v1/webhooks/deliveries`.
Reconnaissez ce type d’événement pour éviter un échec d’enregistrement.
{
"event_id": "018f3c00-0000-7000-8000-0000000000c1",
"event_type": "ping",
"occurred_at": "2026-07-11T07:29:00Z",
"is_testmode": true,
"data": {
"message": "TongKho eSIM webhook health check"
}
}Enveloppe et headers de signature
Tous les webhooks partagent une enveloppe. Signature `HMAC-SHA256(webhook_secret, timestamp\nraw_body)` dans `X-TKE-Webhook-Signature` (hex minuscule). Pendant la rotation de 24 h, ajout de `X-TKE-Webhook-Signature-Old`. Livraison au moins une fois ; dédupliquez par `event_id`.
{
"event_id": "018f3c00-0000-7000-8000-0000000000b0",
"event_type": "order.completed",
"occurred_at": "2026-07-11T07:30:00Z",
"is_testmode": false,
"data": {
"order_id": "018f2b00-0000-7000-8000-00000000009a",
"external_ref": "partner-cart-9931",
"items": [
{
"order_item_id": "018f2b00-0000-7000-8000-000000000001",
"iccid": "8984012345678901234"
}
]
}
}| Champ | Description |
|---|---|
data | Contenu selon le type d'événement (voir ci-dessous). |
event_id | UUID stable, unique par événement (déduplication). |
event_type | Type d'événement. |
is_testmode | true si généré par une clé de test. |
occurred_at | ISO-8601 UTC. |
Synchronisation du catalogue : catalog.updated
Émis sur ajout, modification, masquage, prix de base ou stock. Les changements sont regroupés dans changes[] : des centaines de mises à jour donnent un seul envoi par endpoint.
{
"event_id": "018f3c00-0000-7000-8000-0000000000c1",
"event_type": "catalog.updated",
"occurred_at": "2026-07-11T08:10:00Z",
"is_testmode": false,
"data": {
"changes": [
{
"package_code": "THAY-BANG-MA-GOI-CUA-BAN",
"change": "price",
"occurred_at": "2026-07-11T08:02:11Z"
},
{
"package_code": "VN_10GB_30D",
"change": "availability",
"occurred_at": "2026-07-11T08:04:52Z"
},
{
"package_code": "JP_3GB_7D",
"change": "package",
"occurred_at": "2026-07-11T08:09:30Z"
}
],
"change_count": 3
}
} Les prix sont propres à l’organisation . Un prix unique serait faux et divulguerait des informations. L’événement est un signal : appelez GET /v1/catalog/packages?market=… pour actualiser vos prix.
change accepte trois valeurs : price (coût modifié), availability (stock disponible/épuisé), package (ajout/modification/masquage). Une valeur inconnue doit aussi lancer la synchronisation.
Signature, tentatives et déduplication par event_id suivent les événements habituels, en test et réel, séparés par is_testmode.
Vérifier la signature
X-TKE-Signature contient HMAC-SHA256 calculé avec webhook_secret et les octets bruts du corps.
signing_string = timestamp + "\n" + raw_body # timestamp = header X-TKE-Webhook-Timestamp
signature = HMAC_SHA256(webhook_secret, signing_string) → hex lowercase
# Vérifiez la fraîcheur (±300 s) contre le rejeu.
# Signez les octets reçus, sans resérialiser le JSON.Politique de nouvelles tentatives
Échec non-2xx ou délai de 5 s dépassé : nouvelles tentatives exponentielles sur 24 giờ : 1 phút → 5 phút → 30 phút → 2 giờ → 6 giờ.
4 règles essentielles des webhooks
Notes sur les champs
esim.delivered.lpa est déchiffré avant POST, sans stockage en clair. Créez le QR depuis LPA ou GET /v1/esims/{id}.
esim.usage_threshold: émet uniquement le seuil maximal dépassé (0→95 % : threshold=90).
wallet.low_balance.threshold est un pourcentage (25/10), pas un montant VND.
catalog.updated ne contient ni prix ni stock détaillé : code forfait et type de changement uniquement.