API Postilis · v1

Suivi et webhooks

Suivez chaque courrier, de la validation à la remise aux services postaux, sans interroger l’API en boucle.

Cycle de vie d’un envoi

draft
processing
printed
in_transit
delivered
returned
Courrier retourné à l’expéditeur (adresse inexacte…).
refunded
Envoi impossible : montant recrédité, avoir émis.
cancelled
Brouillon annulé avant paiement.
draft
brouillon
Créé, pas encore payé (envoi en plusieurs étapes, ou crédit insuffisant).
processing
payé
Payé, en cours de préparation. La facture est disponible.
printed
imprimé
Imprimé et mis sous pli.
in_transit
posté
Remis aux services postaux ; estimated_delivery est renseignée (environ 4 jours ouvrables).
delivered
distribué
Distribué, quand l’information est connue. Pour une lettre simple, le suivi s’arrête en général à in_transit.
returned
retourné
Retourné à l’expéditeur.
refunded
remboursé
Le courrier n’a pas pu être expédié : le prix est recrédité et un avoir est émis. Rien n’a été posté.

Consulter un envoi

GET
/orders/{id}
curl https://postilis.fr/api/v1/orders/0b8e2f4c-6f1d-4c1a-9d2e-3a5b7c9d1e2f \ -H "Authorization: Bearer $POSTILIS_API_KEY"
Pour une liste, GET /orders?limit=20 renvoie les envois du plus récent au plus ancien, avec next_cursor pour la page suivante. Si vous interrogez l’API périodiquement, une fois toutes les quelques minutes suffit : les étapes se comptent en heures.

Webhooks

Déclarez une adresse HTTPS dans Mon compte → API et crédit prépayé (une adresse pour les événements de test, une pour les événements réels ; 10 au plus). Postilis y envoie un POST JSON à chaque événement. Un bouton « Envoyer un test » y envoie un événement ping, et l’historique des notifications affiche les réponses de votre serveur.
Notification reçue
POST /postilis HTTP/1.1 Content-Type: application/json User-Agent: Postilis-Webhooks/1.0 Postilis-Event-Id: 5f1c8a2e-0d3b-4f7a-9c21-6e4d2b1a0f99 Postilis-Event-Type: order.status_changed Postilis-Signature: t=1790586131,v1=3b1f…c9a4 { "id": "5f1c8a2e-0d3b-4f7a-9c21-6e4d2b1a0f99", "type": "order.status_changed", "livemode": true, "created_at": "2026-09-28T10:02:11Z", "data": { "order_id": "0b8e2f4c-6f1d-4c1a-9d2e-3a5b7c9d1e2f", "reference": "PST-7K3M9Q", "status": "in_transit", "previous_status": "printed", "tracking_number": null, "estimated_delivery": "2026-10-02" } }
Types d’événements
order.status_changed
envoi
Le statut visible d’un envoi a changé (hors brouillon). data : order_id, reference, status, previous_status, tracking_number, estimated_delivery. Émis pour tous vos envois, y compris ceux créés depuis l’application.
balance.credited
crédit
Une recharge a été créditée. data : topup_id, reference, amount_cents, balance_cents, currency.
ping
test
Envoyé par le bouton « Envoyer un test » (une seule tentative, un seul à la fois par adresse).
balance.credited
{ "id": "…", "type": "balance.credited", "livemode": true, "created_at": "…", "data": { "topup_id": "…", "reference": "REC-8QW2MZ", "amount_cents": 5000, "balance_cents": 9710, "currency": "EUR" } }

Vérifier la signature

Chaque notification porte l’en-tête Postilis-Signature: t=<horodatage>,v1=<signature>. La signature est le HMAC-SHA256, en hexadécimal, de <horodatage>.<corps brut>, calculé avec la clé de signature (whsec_…) affichée à la création de l’adresse. Vérifiez-la sur le corps brut (avant tout décodage JSON), comparez en temps constant et refusez un horodatage de plus de 5 minutes.
import crypto from 'node:crypto'; import express from 'express'; const app = express(); // Corps brut : la signature porte sur les octets reçus, tels quels. app.post('/postilis', express.raw({ type: 'application/json' }), (req, res) => { const entete = req.get('Postilis-Signature') ?? ''; const { t, v1 } = Object.fromEntries(entete.split(',').map((p) => p.split('='))); const attendu = crypto .createHmac('sha256', process.env.POSTILIS_WEBHOOK_SECRET) .update(`${t}.${req.body}`) .digest('hex'); const valide = v1?.length === attendu.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(attendu)) && Math.abs(Date.now() / 1000 - Number(t)) < 300; if (!valide) return res.sendStatus(400); const evenement = JSON.parse(req.body); // Traiter evenement.type une seule fois (evenement.id), puis répondre vite. res.sendStatus(204); });
Clé de signature perdue ou divulguée ?
Dans votre compte, « Nouvelle clé de signature » en génère une autre : les notifications suivantes sont signées avec elle.

Livraison et nouvelles tentatives

  • •
    Répondez par un code 2xx en moins de 10 secondes ; traitez les tâches longues après avoir répondu.
  • •
    Sinon (erreur, délai dépassé, serveur injoignable), Postilis réessaie jusqu’à 14 fois en espaçant les essais (30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 3 h, 4 h, 5 h, puis toutes les 6 h), soit environ 40 heures : un serveur arrêté une nuit ne perd aucune notification.
  • •
    Un même événement peut arriver plusieurs fois : dédoublonnez avec le champ id (aussi dans l’en-tête Postilis-Event-Id).
  • •
    L’ordre d’arrivée n’est pas garanti : fiez-vous à status et, au besoin, relisez l’envoi avec GET /orders/{id}.
  • •
    Les redirections ne sont pas suivies, et seules les adresses publiques sont appelées.
  • •
    Mode test : au-delà de 100 notifications en attente vers une adresse, les suivantes sont marquées en échec sans être envoyées.
Rattrapage
Si votre serveur a été indisponible plus longtemps, relisez vos envois récents avec GET /orders : l’API reste la source de vérité.