Envelopify API

Envelopify envoie de vraies lettres papier avec de vrais timbres depuis votre logiciel, en Belgique. Un appel par lettre : nous imprimons, mettons sous enveloppe, collons les timbres et remettons la lettre à bpost. Standard : remise à bpost dans les 3 jours ouvrables. Express : le jour ouvrable même si reçue avant 12h00 (Europe/Brussels). Flex : dans les 5 jours ouvrables. bpost distribue dans les 2 à 3 jours ouvrables après le dépôt.

URL de basehttps://envelopify.lovable.app/api/v1

Construire avec l'IA

Donnez ce lien à votre assistant IA et laissez-le construire l'intégration. Toute la documentation est disponible en texte brut (en anglais).

Lis la documentation de l'API Envelopify sur https://envelopify.lovable.app/llms-full.txt. Construis dans mon app une intégration qui envoie automatiquement une lettre quand [événement, p. ex. une facture est créée]. Appelle l'API uniquement depuis une fonction serveur, garde la clé API comme secret et jamais dans le navigateur. Commence avec ma clé de test (test_...). Affiche les erreurs de l'API en langage clair à l'utilisateur et conserve l'id et le statut de chaque lettre.

Aussi disponible : openapi.json, developers.md et llms.txt.

Démarrer en 5 minutes

  1. Créer une clé. Créez un compte gratuit, ouvrez Clés API et créez une clé de test. Remplacez test_JOUW_SLEUTEL dans les exemples par votre clé.
  2. Envoyer une lettre test. L'exemple sous Envoyer une lettre utilise notre PDF d'exemple et fonctionne tel quel. Rien n'est imprimé ni facturé.
  3. Consulter le statut. Prenez l'id de la réponse et appelez GET /api/v1/letters/{id}. Ajoutez /pdf pour obtenir le PDF tel que nous l'imprimons.
  4. Configurer les notifications. Enregistrez une URL https avec POST /api/v1/webhooks pour être averti quand votre lettre est imprimée et postée.
  5. Passer en production. Rechargez votre crédit dans le portail et cliquez sur Passer en live à côté de votre clé. Les nouvelles clés démarrent toujours en mode test. La clé elle-même ne change pas.

Créez un compte gratuit pour générer votre clé.

Authentification par clé

Envoyez votre clé avec chaque requête dans l'en-tête Authorization: Bearer <clé>. Les nouvelles clés démarrent en mode test ; vous les passez en live dans le portail (Clés API, Passer en live). Le mode dépend de la clé, pas de son préfixe. Avec une clé en mode test, les lettres ne sont jamais imprimées, envoyées ni facturées ; elles reçoivent le statut received. Une clé n'est affichée qu'une seule fois : gardez-la comme secret sur votre serveur, jamais dans du code navigateur. Une clé manquante, invalide ou révoquée renvoie 401 unauthorized.

Mettre un événement en attentePOST/api/v1/flow-events

Pour un flux de rappel actif et un contact enregistré. Crée un plan unique, pas une lettre immédiate. Le traitement quotidien partage les contrôles de prix et d’arrêt ; un crédit insuffisant conserve la file. event_key évite les doublons par flux et mode. Limites journalières et délai par contact ; les plans test sont exécutés par un administrateur, sans envoi ni frais.

ChampTypeObligatoireDescription
flow_idtexteouiId du flux actif.
contact_idtexteouiId de votre contact.
event_keytexteouiRéférence unique, 1 à 120 caractères.
order_centsnombreMontant pour la condition, 0 par défaut.
variablesobjetChamps nécessaires uniquement.

Exemple de requête

curl -X POST https://envelopify.lovable.app/api/v1/flow-events \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "flow_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "contact_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "event_key": "herinnering-104",
  "variables": {}
}'
202 · Exemple de réponse
{
  "status": "queued",
  "test": true
}

Envoyer une lettrePOST/api/v1/letters

Crée une lettre. Indiquez exactement une source : un PDF (pdf_base64 ou pdf_url) ou un modèle (template_id ou template_code) avec variables. Le PDF suit le choix du compte : page d’adresse gratuite ou zone blanche sur la première page avec options.address_on_first_page. Petit jusqu’à 50 g, moyen jusqu’à 100 g, grand jusqu’à 350 g. Moyen et grand nécessitent un supplément confirmé et activé. La page d’adresse compte pour le poids, pas le prix de base. Renvoie 201 avec l'id, le statut, le prix et son détail (price_breakdown), le nombre de feuilles (page d'adresse comprise), la date de dépôt au plus tard pour la vitesse choisie et test. Avec une clé de test, le statut est received et post_date vaut null. Une lettre refusée renvoie 422 avec un code d'erreur et letter_id. En-tête facultatif Idempotency-Key.

ChampTypeObligatoireDescription
options.address_on_first_pageoui/nonPDF chargé : zone d’adresse blanche opaque fixe sur la première page, sans page supplémentaire. Le contenu couvert devient blanc. Par défaut selon le compte (initialement page séparée).
options.correctionsobjetCorrections PDF : region {left,top,width,height} en mm sur A4, window left/right/both, first_page, white_margins, flatten et a4_portrait. Sans cet objet, la règle du compte s’applique. L’ancienne zone d’adresse devient blanche. L’original est conservé.
recipient.languagetexteFacultatif : nl, fr, en ou de. Choisit la variante enregistrée, sinon le texte de base. Par défaut selon le code postal belge.
recipient.companytexteFacultatif. Nom de l'entreprise.
recipient.nametexteouiNom du destinataire. Le nom ou l'entreprise est obligatoire.
recipient.streettexteouiRue.
recipient.numbertexteouiNuméro.
recipient.boxtexteFacultatif. Numéro de boîte.
recipient.postal_codetexteouiCode postal. 4 chiffres en Belgique.
recipient.citytexteouiCommune.
recipient.countrytexteCode pays (2 lettres), BE par défaut. Aussi tous les autres pays de l'UE (p. ex. NL, FR, DE), +1,50 € par lettre, délai de 5 à 10 jours ouvrables après l'envoi, pas d'express.
pdf_base64textePDF en base64, A4, maximum 20 Mo.
pdf_urltexteLien HTTPS public direct vers votre PDF, maximum 20 Mo. Sans redirection, identifiants, réseau privé ou port non standard.
template_idtexteId d'un modèle, au lieu d'un PDF.
template_codetexteOu le code d'un modèle, p. ex. herinnering.
variablesobjetValeurs des champs de votre modèle, p. ex. { "voornaam": "An" }.
options.flatoui/nonNon plié dans une grande enveloppe, traitement manuel. Disponible après activation. Supplément via /price.
options.registeredoui/nonRecommandé papier, BE et standard/express uniquement, après activation. Suivi : registered_tracking, registered_handed_in_at et registered_receipt_url temporaire.
options.coloroui/nonCouleur. Noir et blanc par défaut. Supplément par feuille via /price.
options.duplexoui/nonRecto verso : deux pages par feuille. Recto par défaut.
options.qr_trackingoui/nonQR code dynamique avec suivi des scans, +0,05 € par lettre.
options.speedtexteVitesse : `standard` (envoyée dans les 3 jours ouvrables), `express` (le jour ouvrable même si reçue avant 12h00, +0,15 € par lettre, minimum 15,00 € par jour) ou `flex` (dans les 5 jours ouvrables, -0,05 € par lettre). `standard` par défaut.
options.qr_target_urltexteObligatoire avec qr_tracking. Lien https vers lequel le QR code redirige. Modifiable ensuite dans le portail.
enclosure_batch_idtexteId des imprimés reçus dans le portail. Un exemplaire par lettre après activation et contrôle. Annulation avant production restitue le stock.
referencetexteFacultatif. Votre propre référence, p. ex. numéro de facture.
post_ontexteJour de dépôt facultatif (YYYY-MM-DD), un jour ouvrable jusqu’à 90 jours calendrier à l’avance. Pas une date de livraison. Crédit réservé par débit à la création, remboursé en cas d’annulation avant production.

Exemple de requête

curl -X POST https://envelopify.lovable.app/api/v1/letters \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Idempotency-Key: factuur-2026-104" \
  -H "Content-Type: application/json" \
  -d '{
  "recipient": {
    "name": "An Peeters",
    "street": "Meir",
    "number": "12",
    "postal_code": "2000",
    "city": "Antwerpen"
  },
  "pdf_url": "https://envelopify.lovable.app/voorbeeld.pdf",
  "options": {
    "color": false
  },
  "reference": "factuur-2026-104"
}'
201 · Exemple de réponse
{
  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "status": "scheduled",
  "price_cents": 129,
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 1,
    "sheets": 1,
    "address_sheet": true,
    "total_sheets": 2,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [],
    "total_cents": 129,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  },
  "sheet_count": 2,
  "post_date": "2026-11-13",
  "free_trial": false,
  "test": false
}

Consulter une lettreGET/api/v1/letters/{id}

Renvoie tous les champs de la lettre, les horodatages, le détail du prix, le nombre de scans QR (scan_count) et, une fois postée, un lien temporaire vers la photo du lot (batch_photo_url, valable 1 heure).

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "tracking_code": "B1D1A7F7",
  "reference": "factuur-2026-104",
  "status": "posted",
  "test": false,
  "reject_reason": null,
  "return_reason": null,
  "recipient": {
    "company": null,
    "name": "An Peeters",
    "street": "Meir",
    "number": "12",
    "box": null,
    "postal_code": "2000",
    "city": "Antwerpen",
    "country": "BE"
  },
  "source": "pdf",
  "color": false,
  "duplex": false,
  "qr_tracking": false,
  "sheet_count": 2,
  "price_cents": 129,
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 1,
    "sheets": 1,
    "address_sheet": true,
    "total_sheets": 2,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [],
    "total_cents": 129,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  },
  "speed": "standard",
  "post_deadline": "2026-11-13",
  "created_at": "2026-11-12T10:02:11Z",
  "scheduled_at": "2026-11-12T10:02:11Z",
  "printed_at": "2026-11-13T09:40:00Z",
  "posted_at": "2026-11-13T16:02:11Z",
  "returned_at": null,
  "post_date": "2026-11-13",
  "scan_count": 0,
  "batch_photo_url": null
}

Télécharger le PDFGET/api/v1/letters/{id}/pdf

Renvoie le PDF définitif tel que nous l'imprimons (application/pdf), page d'adresse comprise. Uniquement pour vos propres lettres. Une lettre refusée n'a pas de PDF et renvoie 404.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029/pdf \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse

Liste des lettresGET/api/v1/letters

Renvoie vos lettres les plus récentes, de la plus récente à la plus ancienne, dans data. S'il y en a plus, vous recevez un next_cursor à renvoyer comme cursor.

FiltreTypeDescription
statustexteFiltrer par statut, p. ex. posted.
referencetexteFiltrer par référence.
fromtexteÀ partir de la date, p. ex. 2026-11-01.
totexteJusqu'à la date incluse.
limitnombre25 par défaut, maximum 100.
cursortexteValeur de next_cursor.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/letters?status=posted&limit=50 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "data": [
    {
      "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
      "status": "posted",
      "reference": "factuur-2026-104"
    }
  ],
  "next_cursor": "MjAyNi0xMS0xMlQxMDo..."
}

Annuler une lettreDELETE/api/v1/letters/{id}

Annule une lettre tant que le lot du jour est encore ouvert. Le montant est recrédité sur votre compte Envelopify, pas versé et la lettre revient avec le statut rejected. Si le lot est déjà clôturé, vous recevez 409 cutoff_passed.

Exemple de requête

curl -X DELETE https://envelopify.lovable.app/api/v1/letters/b1d1a7f7-3db2-43df-a896-bc9dc10c6029 \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse

Calculer un prixPOST/api/v1/price

Calcule le prix d'une lettre avec votre palier actuel, sans rien créer ni facturer. Mêmes options qu'une lettre, mais avec le nombre de pages imprimées au lieu d'un PDF. Avec address_sheet (true par défaut), la page d'adresse gratuite compte dans le maximum de feuilles, comme pour un PDF chargé ; mettez false pour une lettre à partir d'un modèle. Une tranche indisponible renvoie 422 weight_class_unavailable. Utilisez options.address_on_first_page sans page d’adresse supplémentaire.

ChampTypeObligatoireDescription
options.address_on_first_pageoui/nonPDF chargé : zone d’adresse blanche opaque fixe sur la première page, sans page supplémentaire. Le contenu couvert devient blanc. Par défaut selon le compte (initialement page séparée).
options.flatoui/nonNon plié dans une grande enveloppe, traitement manuel. Disponible après activation. Supplément via /price.
options.registeredoui/nonRecommandé papier, BE et standard/express uniquement, après activation. Suivi : registered_tracking, registered_handed_in_at et registered_receipt_url temporaire.
enclosure_batch_idtexteId des imprimés reçus dans le portail. Un exemplaire par lettre après activation et contrôle. Annulation avant production restitue le stock.
pagesnombreouiNombre de pages imprimées (sans la page d'adresse).
address_sheetoui/nonCompter la page d'adresse gratuite dans le maximum de feuilles. true par défaut.
options.countrytextePays de destination, BE par défaut. Un autre pays de l'UE ajoute le supplément UE.
options.coloroui/nonCouleur, supplément par feuille via /price.
options.duplexoui/nonRecto verso.
options.qr_trackingoui/nonQR code avec suivi des scans.
options.speedtextestandard, express ou flex.

Exemple de requête

curl -X POST https://envelopify.lovable.app/api/v1/price \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "pages": 3,
  "address_sheet": true,
  "options": {
    "color": false,
    "duplex": false,
    "qr_tracking": false,
    "speed": "standard"
  }
}'
200 · Exemple de réponse
{
  "price_breakdown": {
    "unit_price_cents": 129,
    "tier": 1,
    "tier_fixed": false,
    "volume_30d": 120,
    "letters_to_next_tier": 379,
    "pages": 3,
    "sheets": 3,
    "address_sheet": true,
    "total_sheets": 4,
    "color": false,
    "duplex": false,
    "speed": "standard",
    "destination": "BE",
    "options": [
      {
        "code": "extra_sheets",
        "label": "Extra vellen (2)",
        "amount_cents": 20
      }
    ],
    "total_cents": 149,
    "on_request": false,
    "currency": "EUR",
    "vat_included": false
  }
}

Liste des modèlesGET/api/v1/templates

Renvoie vos modèles dans data.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/templates \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "data": [
    {
      "id": "6c1f...",
      "name": "Herinnering",
      "code": "herinnering",
      "body": "Beste {{voornaam}}, ...",
      "created_at": "2026-11-12T10:00:00Z"
    }
  ]
}

Créer un modèlePOST/api/v1/templates

Crée un modèle. Les champs se placent entre doubles accolades, p. ex. {{voornaam}}. Un code existant renvoie 409 conflict.

ChampTypeObligatoireDescription
nametexteouiNom.
codetexteouiCode unique : lettres, chiffres, - et _.
bodytexteouiTexte avec champs.

Exemple de requête

curl -X POST https://envelopify.lovable.app/api/v1/templates \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Herinnering",
  "code": "herinnering",
  "body": "Beste {{voornaam}},\n\nWe ontvingen nog geen betaling van {{bedrag}}."
}'
201 · Exemple de réponse

Crédit et tarifsGET/api/v1/balance

Renvoie votre crédit, les lettres d'essai gratuites restantes (free_letters_remaining), votre palier actuel, le nombre de vraies lettres des 30 derniers jours, les lettres restantes avant la remise suivante et la grille tarifaire en cents, HTVA : prix par lettre par palier, suppléments des options, vitesses et règles express.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/balance \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "credit_cents": 4950,
  "free_letters_remaining": 3,
  "company_verified": true,
  "tier": 1,
  "tier_fixed": false,
  "volume_30d": 120,
  "letters_to_next_tier": 380,
  "unit_price_cents": 129,
  "tiers": [
    {
      "tier": 1,
      "from": 0,
      "unit_price_cents": 129
    },
    {
      "tier": 2,
      "from": 500,
      "unit_price_cents": 109
    },
    {
      "tier": 3,
      "from": 5000,
      "unit_price_cents": 99
    }
  ],
  "options": {
    "extra_sheet_cents": 10,
    "qr_tracking_cents": 5
  },
  "speeds": [
    {
      "speed": "standard",
      "name": "Standaard",
      "amount_cents": 0,
      "working_days": 3
    },
    {
      "speed": "express",
      "name": "Express",
      "amount_cents": 15,
      "working_days": 0
    },
    {
      "speed": "flex",
      "name": "Flex",
      "amount_cents": -5,
      "working_days": 5
    }
  ],
  "express": {
    "minimum_cents": 1500,
    "max_letters_per_day": 1000,
    "before_hour": 12
  },
  "heavy_letter": {
    "from_grams": 50,
    "amount_cents": 60
  },
  "eu": {
    "surcharge_cents": 150,
    "countries": [
      "AT",
      "DE",
      "FR",
      "NL",
      "..."
    ],
    "delivery": "5 tot 10 werkdagen na verzending",
    "express": false
  },
  "daily_letter_max": 1000,
  "max_sheets": 8,
  "currency": "EUR",
  "vat_included": false
}

Votre compte et vérification de l'entrepriseGET/api/v1/account

Renvoie les données de votre propre compte. company_verified vaut true si votre numéro de TVA a été vérifié via le contrôle européen de la TVA (VIES) ou manuellement par nous. verification_status vaut verified, none (non vérifiée), pending (vérification plus tard) ou invalid. Pour une entreprise vérifiée, official_name et official_address proviennent du registre officiel. GET /balance contient aussi company_verified. Le statut ne se modifie pas via l'API : uniquement après un vrai contrôle dans le portail.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/account \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "customer_id": "4f6c2a1e-...",
  "company": "Jouw Bedrijf",
  "email": "post@jouwbedrijf.be",
  "company_verified": true,
  "verification_status": "verified",
  "vat_number": "BE0123456749",
  "official_name": "Jouw Bedrijf BV",
  "official_address": "Kerkstraat 1\n2000 Antwerpen",
  "checked_at": "2026-11-12T10:00:00Z",
  "free_letters_remaining": 3
}

Liste des webhooksGET/api/v1/webhooks

Liste vos URL et notification_groups. null signifie tous les événements existants ; une liste limite les notifications aux groupes choisis.

Exemple de requête

curl -X GET https://envelopify.lovable.app/api/v1/webhooks \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "data": [
    {
      "id": "a3c2...",
      "url": "https://jouwsite.be/hooks/envelopify",
      "active": true,
      "created_at": "2026-11-12T10:00:00Z"
    }
  ]
}

Créer un webhookPOST/api/v1/webhooks

Enregistre une URL https. La réponse contient une seule fois le secret ; utilisez-le pour vérifier les signatures.

ChampTypeObligatoireDescription
urltexteouiLien https qui reçoit notre POST.
notification_groupslisteFacultatif : problems (review/rejected), sent (posted/registered_handed_in), undeliverable (returned), delivered (bientôt, aucun événement). Omettre conserve tous les événements ; [] ne reçoit rien. Modifiable par URL dans le portail.

Exemple de requête

curl -X POST https://envelopify.lovable.app/api/v1/webhooks \
  -H "Authorization: Bearer test_JOUW_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://jouwsite.be/hooks/envelopify"
}'
201 · Exemple de réponse
{
  "id": "a3c2...",
  "url": "https://jouwsite.be/hooks/envelopify",
  "active": true,
  "created_at": "2026-11-12T10:00:00Z",
  "secret": "whsec_..."
}

Supprimer un webhookDELETE/api/v1/webhooks/{id}

Supprime un webhook.

Exemple de requête

curl -X DELETE https://envelopify.lovable.app/api/v1/webhooks/a3c2... \
  -H "Authorization: Bearer test_JOUW_SLEUTEL"
200 · Exemple de réponse
{
  "id": "a3c2...",
  "deleted": true
}

Notifications de statut

À chaque événement, nous envoyons un POST { event, letter_id, status, occurred_at, letter } à toutes vos URL de webhook actives. Chaque requête porte l'en-tête x-envelopify-signature : le HMAC-SHA256 hexadécimal du corps brut avec votre secret. Vérifiez-le avant de faire confiance au contenu et répondez avec un statut 2xx. Les envois échoués (pas de 2xx ou pas de réponse dans les 8 secondes) sont relancés jusqu'à 3 fois (lors de votre événement suivant et à chaque tournée de production), soit 4 tentatives au total. Les lettres test restent received et n'envoient pas de webhooks.

ÉvénementQuand
letter.scheduledPlanifiée pour un jour de dépôt, crédit débité
letter.reviewAdresse à contrôler : nous la vérifions avant l'impression
letter.checkedAdresse approuvée après contrôle, planifiée
letter.rejectedRefusée après contrôle ou annulée avant production, crédit remboursé
letter.printedImprimée et affranchie
letter.postedRemise à bpost
letter.returnedRevenue comme non distribuable
letter.registered_handed_inDépôt recommandé avec suivi et preuve
letter.scannedQR code scanné
{
  "event": "letter.posted",
  "letter_id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
  "status": "posted",
  "occurred_at": "2026-11-13T16:02:12Z",
  "letter": {
    "id": "b1d1a7f7-3db2-43df-a896-bc9dc10c6029",
    "tracking_code": "B1D1A7F7",
    "reference": "factuur-2026-104",
    "status": "posted",
    "posted_at": "2026-11-13T16:02:11Z"
  },
  "created_at": "2026-11-13T16:02:12Z"
}

Vérifier la signature :

// Node 18+ with Express: verify a webhook signature
import crypto from "node:crypto";
import express from "express";

const SECRET = "whsec_JOUW_GEHEIM"; // returned once when you create the webhook
const app = express();

app.post("/hooks/envelopify", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
  const given = req.get("x-envelopify-signature") ?? "";
  const ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).end();
  const { event, letter } = JSON.parse(req.body.toString("utf8"));
  console.log(event, letter.id, letter.status);
  res.status(200).end();
});

app.listen(3000);

Éviter les doublons

Envoyez un en-tête Idempotency-Key, p. ex. votre numéro de facture. Si vous renvoyez la même clé dans les 24 heures, vous recevez la lettre existante (statut 200) au lieu d'une nouvelle, et rien n'est facturé en plus. Après 24 heures, la clé compte comme nouvelle.

Statuts

StatutSignification
receivedReçue. Les lettres test restent dans ce statut.
scheduledPlanifiée pour un jour de dépôt, crédit débité.
reviewÀ contrôler : le code postal n'existe pas ou ne correspond pas à la commune. Nous approuvons ou refusons l'adresse (crédit remboursé). Voir address_warning.
printedImprimée et affranchie.
postedRemise à bpost.
returnedRevenue comme non distribuable.
rejectedRefusée ou annulée. La raison figure dans reject_reason.

Codes d'erreur

Les erreurs ont toujours la forme { error: { code, message } }. Le code (error.code) est toujours le même, quelle que soit la langue : utilisez-le dans votre logique. Le message (error.message) est en néerlandais et peut être affiché tel quel à votre utilisateur.

CodeHTTPSignification
sender_not_verified422Tout envoi réel, même gratuit, exige un e-mail confirmé et une entreprise vérifiée. Le mode test reste disponible.
weight_class_unavailable422Tranche non activée ou poids supérieur à 350 grammes.
service_unavailable422Le service choisi ou le stock reçu n’est pas disponible.
invalid_post_date422Le jour de dépôt n’est pas un jour ouvrable valable dans les 90 jours.
address_incomplete422Un champ d'adresse manque ou le code postal est incorrect.
insufficient_balance422Votre crédit ne suffit pas.
pdf_invalid422Le PDF ne peut pas être ouvert, n'est pas un PDF ou dépasse 20 Mo.
too_many_sheets422Plus de 8 feuilles, page d'adresse comprise.
express_limit422Plus de 1000 lettres express sur une journée.
country_not_supported422Pays hors de la Belgique et de l'UE.
express_not_available422L'express n'est pas disponible vers les autres pays de l'UE.
daily_limit422Maximum journalier de lettres atteint (1000 par défaut, ajustable sur demande).
template_invalid422Modèle introuvable ou champs manquants.
invalid_request400Requête invalide, p. ex. source manquante ou lien QR incorrect.
unauthorized401Clé manquante, invalide ou révoquée.
not_found404La lettre, le webhook ou le point d'accès n'existe pas. Aussi sans clé.
cutoff_passed409Trop tard pour annuler, la lettre est en production.
not_cancellable409Annulation impossible, p. ex. lettre déjà refusée ou annulée.
conflict409Un modèle avec ce code existe déjà.
rate_limited429Plus de 60 requêtes par minute. Voyez l'en-tête Retry-After.
server_error500Un problème est survenu de notre côté.
{
  "error": {
    "code": "address_incomplete",
    "message": "Een Belgische postcode bestaat uit 4 cijfers"
  }
}

Limites

Maximum 60 requêtes par minute par clé ; au-delà, vous recevez 429 rate_limited avec un en-tête Retry-After. PDF jusqu'à 20 Mo, lettres jusqu’à 350 g, moyen et grand après activation, maximum 1000 lettres express par jour et par défaut maximum 1000 lettres par jour par compte. error.code est indépendant de la langue et stable ; message est en néerlandais. CORS est ouvert, mais n'utilisez jamais votre clé dans du code navigateur.

Tarifs

Un seul prix par lettre, HTVA, tout compris : 1 feuille (recto ou recto verso, noir et blanc), enveloppe, vrais timbres et dépôt. Selon vos vraies lettres des 30 derniers jours glissants, nouvelle lettre incluse, sans remise à zéro mensuelle : 1,29 € sous 500, 1,09 € à partir de 500, 0,99 € à partir de 5000. Couleur : +0,08 € par feuille. Recto verso sans supplément. Feuille supplémentaire : +0,10 € par feuille après la première (en recto verso, deux pages par feuille). QR code avec suivi des scans : +0,05 €. La page d'adresse est gratuite et ne compte pas dans le prix. Une lettre standard compte jusqu'à 8 feuilles au total, page d'adresse comprise ; les lettres plus grandes sont possibles sur demande. Vitesses (options.speed) : standard (remise à bpost dans les 3 jours ouvrables), express (+0,15 €, le jour ouvrable même si reçue avant 12h00 Europe/Brussels, sinon le jour ouvrable suivant) et flex (-0,05 €, dans les 5 jours ouvrables). bpost distribue dans les 2 à 3 jours ouvrables après le dépôt. Minimum express via l'API : si vos lettres express d'une journée totalisent moins de 15,00 € à 12h00, la différence est débitée comme ligne "Aanvulling minimum express" ; votre crédit doit la couvrir lors de la création de la lettre. Maximum 1000 lettres express par jour (422 express_limit). Exemples, palier 1 : 1 feuille 1,29 € ; 1 feuille couleur 1,37 € ; 3 feuilles 1,49 € ; 4 pages recto verso (2 feuilles) 1,39 € ; avec QR 1,34 € ; express 1,44 € ; flex 1,24 €. Chaque réponse avec un prix contient price_breakdown avec unit_price_cents, tier, volume_30d, letters_to_next_tier, sheets, total_sheets, speed, options (liste de code, label, amount_cents) et total_cents. Les prix peuvent changer ; lisez-les via GET /balance ou POST /price. Lettres d'essai gratuites : chaque nouveau compte reçoit 3 vraies lettres gratuites, aussi via l'API avec votre clé en mode live. Conditions : vitesse standard, 3 feuilles de contenu maximum, adresse e-mail confirmée et 3 lettres d'essai maximum par adresse de destinataire. Une lettre qui ne remplit pas les conditions est payée avec votre crédit comme d'habitude. Une lettre d'essai a free_trial: true et price_cents: 0 ; l'annuler avant la production la rend. GET /balance affiche free_letters_remaining. Lettre lourde : au-delà de 50 g, nous ajoutons +0,60 € par lettre (option heavy_letter). Autres pays de l'UE : +1,50 € par lettre (option eu), délai de 5 à 10 jours ouvrables après l'envoi, pas d'express. Contrôle d'adresse : pour une adresse belge, nous vérifions que le code postal existe et correspond à la commune. En cas de doute, la lettre reçoit le statut review avec address_warning et nous l'approuvons ou la refusons (crédit remboursé).

Tous les tarifs et le calculateur se trouvent sur la page des tarifs.