Bjet24

Webhooks Bjet24 : suivre en temps réel le statut de chaque envoi postal

Plutôt que de polluer notre API avec du polling, branchez un webhook : pour chaque envoi, vous recevez les transitions (créé, déposé, en distribution, remis, accusé reçu). Voici la structure des payloads, la vérification de signature, et un exemple de handler Next.js prêt à coller.

11 mai 20267 min de lecture

Pourquoi un webhook (et pas du polling)

Une fois que vous avez intégré l'API Bjet24 pour automatiser vos envois, la question suivante arrive vite : comment savoir où en est chaque pli sans interroger l'API toutes les 30 secondes ?

Le polling fonctionne, mais c'est cher (en coût d'API, en latence et en code à entretenir) et imprécis. La réponse propre : les webhooks. Vous nous indiquez une URL HTTPS, nous vous appelons à chaque transition d'état avec un payload signé. Votre application reste passive et reçoit l'information dans la seconde.

Les événements suivis

Bjet24 expose les événements suivants pour les envois postaux :

  • mailing.created — l'envoi a été créé via l'API,
  • mailing.printed — l'envoi est imprimé en centre d'impression,
  • mailing.handed_over — l'envoi a été remis à bpost,
  • mailing.in_delivery — l'envoi est dans la tournée du facteur,
  • mailing.delivered — l'envoi a été remis au destinataire,
  • mailing.return_receipt — l'accusé de réception a été signé (recommandé uniquement),
  • mailing.failed — échec de distribution (avis de passage, refus, NPAI).

Chaque événement inclut un mailing_id, un status, un horodatage en UTC ISO 8601 et un objet metadata libre que vous avez fourni à la création (par exemple un invoice_id interne).

Configurer un endpoint

Côté Bjet24 : vous déclarez un endpoint dans la console développeur ou via l'API. Vous y associez :

  • une URL HTTPS publique (TLS 1.2 minimum),
  • un secret que nous utilisons pour signer chaque requête,
  • la liste des événements que vous voulez recevoir.

Côté votre application, le handler doit :

  1. recevoir un POST JSON,
  2. vérifier la signature X-Bjet24-Signature,
  3. répondre 2xx rapidement (sous 5 s),
  4. traiter l'événement de manière idempotente (le event_id est unique).

La structure du payload

{
  "event_id": "evt_01HYZ...",
  "event_type": "mailing.delivered",
  "occurred_at": "2026-05-11T08:42:13Z",
  "data": {
    "mailing_id": "ml_01HYX...",
    "status": "delivered",
    "tracking_number": "ZA123456789BE",
    "recipient": {
      "name": "ACME SPRL",
      "address_line_1": "Rue de la Loi 16",
      "postal_code": "1000",
      "city": "Brussels"
    },
    "metadata": {
      "invoice_id": "INV-2026-0421"
    }
  }
}

Le champ metadata est un objet JSON arbitraire passé à la création de l'envoi. Utilisez-le pour faire le pont vers vos identifiants internes sans avoir à maintenir une table de correspondance.

La signature HMAC

Chaque requête contient un header X-Bjet24-Signature au format t=TIMESTAMP,v1=SIGNATURE. La signature est un HMAC-SHA256 calculé sur la chaîne TIMESTAMP.RAW_BODY avec votre secret.

Vérification recommandée :

  • rejeter les requêtes dont le timestamp s'écarte de plus de 5 minutes de l'heure serveur (protection contre les replays),
  • comparer les signatures en temps constant (timingSafeEqual).

Exemple : handler Next.js (App Router)

// app/api/webhooks/bjet24/route.ts
import { createHmac, timingSafeEqual } from "node:crypto"
import { NextRequest } from "next/server"

const SECRET = process.env.BJET24_WEBHOOK_SECRET!

export async function POST(req: NextRequest) {
  const raw = await req.text()
  const header = req.headers.get("x-bjet24-signature") ?? ""

  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  )
  const ts = parts.t
  const sig = parts.v1
  if (!ts || !sig) return new Response("missing signature", { status: 400 })

  const tsNum = Number(ts)
  if (Math.abs(Date.now() / 1000 - tsNum) > 300) {
    return new Response("stale timestamp", { status: 400 })
  }

  const expected = createHmac("sha256", SECRET)
    .update(`${ts}.${raw}`)
    .digest("hex")

  const a = Buffer.from(expected, "hex")
  const b = Buffer.from(sig, "hex")
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return new Response("invalid signature", { status: 401 })
  }

  const event = JSON.parse(raw) as {
    event_id: string
    event_type: string
    data: { mailing_id: string; status: string; metadata?: Record<string, string> }
  }

  // Idempotence : ignorez si event_id déjà traité.
  // Puis branchez sur event.event_type.
  if (event.event_type === "mailing.delivered") {
    // ...mettez à jour votre facture / dossier client...
  }

  return new Response("ok", { status: 200 })
}

Politique de retry

Si votre endpoint répond 5xx ou ne répond pas, nous réessayons en exponentiel pendant 24 heures (typiquement 6 à 8 retries). Tant que vous renvoyez 2xx, nous considérons l'événement comme livré.

Conséquence pratique : votre handler doit être idempotent. Le même event_id peut arriver plusieurs fois (par exemple si vous avez répondu 2xx mais que la connexion a été coupée juste avant que nous le voyions). Stockez les event_id traités et ignorez les doublons.

Trois bonnes pratiques

  • Loggez tout. Conservez les payloads bruts au moins quelques jours. C'est inestimable pour déboguer un statut inattendu.
  • Découplez le traitement. Répondez 200 immédiatement, puis poussez le travail réel dans une file (BullMQ, SQS, etc.). Vous évitez les retries qui s'accumulent.
  • Versionnez votre handler. Quand vous modifiez la logique métier, déployez le nouveau handler à une URL versionnée et migrez progressivement.

En résumé

Brancher un webhook Bjet24 prend 30 minutes : un endpoint signé, une vérification HMAC, un traitement idempotent. En échange, vous obtenez une visibilité temps réel sur tous vos envois postaux en Belgique — sans dette d'API et sans cron à entretenir. Pour une PME qui gère de nombreux envois en lot, c'est le moyen le plus simple de transformer "le courrier est parti" en information exploitable dans votre CRM ou votre ERP.

Questions fréquentes

Comment vérifier la signature d'un webhook Bjet24 ?

Chaque requête envoyée par Bjet24 contient un header X-Bjet24-Signature au format t=TIMESTAMP,v1=SIGNATURE. Pour vérifier l'authenticité, calculez un HMAC-SHA256 sur la chaîne TIMESTAMP.CORPS_BRUT avec votre secret, puis comparez le résultat en temps constant avec timingSafeEqual. Rejetez toute requête dont le timestamp s'écarte de plus de 5 minutes de votre horloge serveur pour vous protéger contre les attaques par rejeu.

Que se passe-t-il si mon endpoint webhook ne répond pas ?

Si votre endpoint renvoie un code 5xx ou ne répond pas dans le délai imparti, Bjet24 réessaie automatiquement avec un backoff exponentiel pendant 24 heures, soit typiquement 6 à 8 tentatives. Dès que votre endpoint renvoie un code 2xx, l'événement est marqué comme livré et les retries s'arrêtent. Il est donc conseillé de répondre 200 immédiatement et de déléguer le traitement à une file asynchrone.

Comment éviter de traiter deux fois le même événement webhook ?

Chaque événement possède un identifiant unique event_id. Stockez les identifiants déjà traités (en base de données ou dans un cache Redis) et ignorez silencieusement les événements dont l'identifiant est déjà présent. Cette idempotence est indispensable car un même événement peut être envoyé plusieurs fois en cas de perte de connexion entre la livraison et votre réponse 2xx.

Quels événements de statut postal puis-je recevoir via les webhooks ?

Les webhooks couvrent toutes les transitions clés d'un envoi postal : création via l'API (mailing.created), impression (mailing.printed), remise à bpost (mailing.handed_over), mise en tournée (mailing.in_delivery), livraison au destinataire (mailing.delivered), signature de l'accusé de réception (mailing.return_receipt) et échec de distribution (mailing.failed). Vous choisissez les événements qui vous intéressent lors de la déclaration de l'endpoint.

Puis-je tester un endpoint webhook localement avant de le déployer ?

Oui. Utilisez un outil comme ngrok ou Cloudflare Tunnel pour exposer temporairement votre serveur local via une URL HTTPS publique. Déclarez cette URL dans la console développeur, déclenchez des envois de test et observez les payloads reçus. Cette approche vous permet de valider la logique de vérification de signature et le traitement des événements sans déployer en production.

Prêt à envoyer un courrier ?

Envoyez votre lettre ou recommandé en quelques minutes, sans imprimer, sans bureau de poste.

Comment ça marche

Articles similaires

Commentaires (0)

Chargement des commentaires…

Laisser un commentaire

Votre commentaire sera publié après validation par un modérateur.