Skip to content

Webhooks -- Vue d'ensemble

Les webhooks permettent à votre application de recevoir des notifications en temps réel sur les événements de la plateforme Owem Pay. Quand un événement se produit, Owem Pay envoie un HTTP POST à l'URL enregistrée.

Comment ça fonctionne

  1. Enregistrez une URL de webhook dans votre compte
  2. Quand un événement se produit (ex. : PIX reçu), Owem Pay envoie un HTTP POST à votre URL
  3. Votre application traite la notification et répond avec un status 2xx (200, 201 ou 204)

Événements disponibles

Owem Pay livre uniquement des événements liés au PIX. D'autres produits (boleto, comptes, STA, virements non-PIX) ne sont pas dans le périmètre. Toute tentative d'abonnement à des événements hors du tableau ci-dessous est rejetée avec events: contains invalid events: ....

ÉvénementStatus bodyDescriptionDéclenchement
pix.charge.createdcreatedQR code généré ou cash-in initiéActif
pix.charge.paidpaidPIX reçu et liquidéActif
pix.charge.expiredexpiredQR code expiré sans paiement (vérifié toutes les 5 min par worker)Actif
pix.charge.cancelledcancelledQR code annulé avant le paiementEnregistré, pas encore déclenché
pix.payout.queuedqueuedPIX envoyé mis en file par rate limit (ClientLimiter par merchant ou bucket DICT BACEN). Retry automatique toutes les ~3s, TTL maximum 2hActif
pix.payout.processingprocessingPIX envoyé, en attente de confirmation BACENActif
pix.payout.confirmedsettledPIX envoyé et confirmé (terminal)Actif
pix.payout.failedrejectedPIX envoyé rejeté par le SPI (terminal)Actif
pix.payout.returnedreturnedPIX envoyé rembourséActif
pix.refund.requestedrequestedDemande de remboursement reçue (infraction BACEN) ; blocage conservatoire créé sur le solde du clientActif
pix.refund.completedsettled / completedAnalyse de la défense finalisée et remboursement exécuté (ou libéré)Actif
pix.return.receivedsettledRemboursement PIX reçu (crédit)Actif
pix.infraction.createdACKNOWLEDGEDInfraction PIX signalée par la contrepartie via BACEN DICT ; exige une défense ou génère un blocage conservatoire automatique (>R$1k)Actif
pix.infraction.resolvedCLOSED / CANCELLEDInfraction résolue (admin close, auto-deny ou contrepartie a annulé)Actif
pix.infraction.defense_submitteddefense_submittedDéfense soumise par le merchant (portail ou API) ; attend l'analyse BACENActif
webhook.testtestTest manuel. Disponible uniquement via Admin/Merchant portal — l'External API n'expose pas d'endpoint pour déclencher un testDéclenchement manuel (pas External API)

pix.charge.cancelled n'est pas encore déclenché

L'événement est dans l'enum et peut être souscrit, mais le système n'a pas de flux d'annulation de QR code aujourd'hui. Si vous vous abonnez, le POST /webhooks répond 201 normalement — mais aucune notification n'arrivera. Continuez à surveiller pix.charge.expired pour le cycle naturel de vie du QR.

Sécurité

Chaque notification inclut des headers de sécurité et d'identification pour la validation :

HeaderDescription
X-Owem-SignatureSignature HMAC-SHA256 du payload (préfixe sha256=). Dans des cas rares (webhook enregistré sans secret), la valeur littérale est unsigned — voir note ci-dessous
X-Owem-TimestampUnix timestamp en secondes de l'envoi
X-Owem-Event-IdUUID unique de la delivery (pour déduplication)
X-Owem-Event-TypeType de l'événement (ex. : pix.charge.paid)
Content-TypeToujours application/json
User-AgentToujours Owem-Webhook/1.0 — utilisez pour le whitelisting dans firewalls/WAF. L'évolution future suivra le pattern Owem-Webhook/{version} ; filtrez par préfixe Owem-Webhook/ pour être immunisé aux nouvelles versions

Signature unsigned quand le webhook n'a pas de secret

Si le webhook a été enregistré sans champ secret (scénario legacy), le header X-Owem-Signature vaut littéralement unsigned. Cela désactive la validation HMAC de votre côté. En pratique, POST /api/external/webhooks génère un secret aléatoire de 64 caractères quand le champ est omis (depuis session 80), donc le scénario n'apparaît que dans des enregistrements très anciens ou via admin bypass. Si vous recevez unsigned, enregistrez un nouveau webhook avec secret explicite et supprimez l'ancien.

SHA256 dans les webhooks vs SHA512 dans l'API

L'API utilise HMAC-SHA512 pour authentifier les requêtes que vous envoyez. Les webhooks envoyés par Owem Pay utilisent HMAC-SHA256 dans la signature X-Owem-Signature. Ce sont des algorithmes différents -- chacun dans son contexte.

Validation de la signature

Validez la signature pour garantir que la notification a été envoyée par Owem Pay :

javascript
const crypto = require('crypto');

function validateWebhook(rawBody, timestamp, signature, secret) {
  // rawBody is the RAW request body string (before any JSON parse)
  // timestamp is unix seconds (e.g., 1712160000)
  const message = `${timestamp}.${rawBody}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Utilisez le body RAW, pas re-sérialisé

Vous devez utiliser le corps exact de la requête HTTP tel que les octets sont arrivés dans votre application. Si vous faites JSON.parse puis JSON.stringify, les octets résultants ne seront pas identiques à ceux qu'Owem a utilisé pour signer, et la validation échouera.

En Express/Node : utilisez express.raw({ type: 'application/json' }) ou gardez le body avant tout middleware de parse.

Dans d'autres frameworks : configurez pour capturer le raw body avant le middleware JSON.

Tri des clés dans les WEBHOOKS : PAS nécessaire

Pour la validation des webhooks (HMAC-SHA256) vous N'avez PAS besoin de trier les clés — utilisez le body raw tel que reçu dans le HTTP request d'Owem.

⚠️ Attention — différence vs envoi de requêtes : Dans la signature HMAC-SHA512 des REQUÊTES que vous envoyez, le tri alphabétique des clés EST obligatoire (le serveur Owem réordonne avant de valider). Ne confondez pas les deux scénarios :

  • Webhook reçu (HMAC-SHA256) : validez le body raw sans réordonner
  • Request envoyée (HMAC-SHA512) : triez vos clés alphabétiquement avant de signer

Validez toujours

Ne traitez jamais un webhook sans valider la signature. Cela protège contre les requêtes falsifiées.

De plus, validez que le X-Owem-Timestamp est dans ± 5 minutes de l'heure actuelle (protection anti-replay — le serveur ne rejette pas les webhooks « anciens » par défaut ; cette vérification appartient à votre endpoint comme défense en profondeur) et dédupliquez les événements par X-Owem-Event-Id (protection contre les retries).

Retry Policy

Si votre URL retourne un status différent de 2xx (ou timeout après 30 s), Owem Pay effectue jusqu'à 8 tentatives avec backoff exponentiel. Le total du temps entre la première et la huitième tentative est d'environ 7h45min :

TentativeDélai depuis la tentative précédenteTemps cumulé
1re— (immédiat, eager via Task.start)~50–200 ms
2e30 secondes~30 s
3e2 minutes~2,5 min
4e10 minutes~12,5 min
5e30 minutes~42,5 min
6e1 heure~1,75 h
7e2 heures~3,75 h
8e4 heures~7,75 h

Après 8 tentatives sans succès, le webhook_delivery est marqué avec le status failed et n'est pas renvoyé automatiquement. Vous pouvez demander un replay manuel au support Owem en fournissant le X-Owem-Event-Id (ou avoir un opérateur avec accès admin qui replay via le portail).

Status d'une delivery

Chaque delivery passe par les status : pending (créée, en attente de livraison) → delivered (2xx reçu) OU failed (8 tentatives épuisées) OU expired (replay protection).

À propos d'expired : quand le worker Oban va traiter la première tentative et que la delivery a déjà plus de 5 minutes depuis sa création (inserted_at), l'envoi est abandonné et le status passe directement à expired. Cela empêche que des retraitements tardifs (par accumulation de file, pod redémarré, etc.) déclenchent des notifications d'événements déjà anciens. Les replays manuels demandés au support Owem passent par le flag interne manual_replay: true et bypass ce guard — le client reçoit la notification normalement.

Durabilité

Avant de tenter la première livraison, l'événement est persisté dans webhook_deliveries dans PostgreSQL. Si le pod qui déclenche le webhook tombe pendant la livraison, Oban reprend automatiquement au prochain retry — aucun événement n'est perdu.

Idempotence

Votre application doit être idempotente : si elle reçoit le même événement plus d'une fois (identifié par X-Owem-Event-Id), elle doit le traiter sans dupliquer les effets.

Replay manuel via admin

Si une delivery a échoué et que vous devez la renvoyer, l'équipe Owem peut exécuter un replay manuel via le dashboard admin. Contactez le support avec le event_id de la delivery.

Livraisons dupliquées (race condition connue)

Le système utilise un mécanisme de « eager delivery » pour accélérer la première livraison + un Oban worker comme fallback durable de retry. Dans les scénarios de forte concurrence, ces deux chemins peuvent déclencher le même webhook en parallèle (fenêtre de ~1 seconde). Dans cette situation vous recevez le même payload 2 fois via HTTP, mais avec le même X-Owem-Event-Id — c'est le même événement, pas un retry.

Pour éviter l'impact dupliqué :

  • Dédupliquez par X-Owem-Event-Id (recommandé — UUID unique par delivery, stable dans les retries et dans la race condition ci-dessus)
  • Ou alternativement dédupliquez par end_to_end_id + event_type quand cela a du sens pour l'événement

C'est un comportement attendu, pas une erreur. Les retries légitimes (après 5xx/timeout) réutilisent également le même X-Owem-Event-Id.

External ID dans les webhooks

Quand une transaction a été créée avec external_id, ce champ est inclus dans le payload du webhook à l'intérieur de l'objet data. Utilisez-le pour corréler l'événement avec la commande dans votre système sans avoir besoin de faire une consultation supplémentaire.

Exigences de l'endpoint

  • L'URL doit utiliser HTTPS (sauf si allow_insecure: true dans l'enregistrement)
  • Doit répondre avec le status 2xx dans les 30 secondes (receive_timeout configuré dans le worker de livraison)
  • Le body de la réponse est ignoré
  • Recommandé de répondre rapidement (200 OK immédiat) et traiter l'événement de façon asynchrone de votre côté ; les délais longs réduisent le throughput et augmentent la chance de retries

Étapes suivantes

Owem Pay Instituição de Pagamento — ISPB 37839059