Envoyer une facture ou un reçu en PDF par WhatsApp

Oui, avec un template dont l'en-tête est un DOCUMENT : le PDF d'exemple se joint à la création, le vrai PDF s'envoie par une URL https publique dans header_document_url.

Envoyer une facture ou un reçu en PDF par WhatsApp

Astuce
Oui, avec un template dont l'en-tête est un DOCUMENT : le PDF d'exemple se joint à la création, le vrai PDF s'envoie par une URL https publique dans header_document_url.

Scénario

Un client paie au comptoir : vous voulez lui envoyer son reçu ou sa facture en PDF sur WhatsApp, avec un petit message.

Oui. Il n'y a pas de « pièce jointe libre » hors des 24 h : le PDF voyage dans l'en-tête d'un template. À la création vous donnez un PDF d'exemple (pour que Meta approuve la forme du message) ; à chaque envoi vous donnez l'adresse https du vrai PDF de ce client.

Conversation

Vous    : POST /v1/messages  to=+237690000001  variables=["Awa", "F-2026-0412"]
          header_document_url=https://files.example.com/factures/F-2026-0412.pdf
          header_document_filename=facture-F-2026-0412.pdf
Awa     : [ facture-F-2026-0412.pdf ]
          « Bonjour Awa, voici votre facture F-2026-0412. Merci de votre confiance. »

Code

  1. Créez le template. header_media_base64 est votre PDF d'exemple, encodé en base64 (le nom header_image_base64 est accepté aussi) ; header_media_mime dit que c'est un PDF :

curl

curl -X POST "https://wa-api.geskap.com/v1/templates" \
  -H "Authorization: Bearer $GESKAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "facture_pdf",
  "language": "fr",
  "category": "UTILITY",
  "body_text": "Bonjour {{1}}, voici votre facture {{2}}. Merci de votre confiance.",
  "example_values": ["Awa", "F-2026-0412"],
  "header_media_base64": "<VOTRE_PDF_D_EXEMPLE_EN_BASE64>",
  "header_media_mime": "application/pdf"
}'

Node.js

const res = await fetch("https://wa-api.geskap.com/v1/templates", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "facture_pdf",
    "language": "fr",
    "category": "UTILITY",
    "body_text": "Bonjour {{1}}, voici votre facture {{2}}. Merci de votre confiance.",
    "example_values": ["Awa", "F-2026-0412"],
    "header_media_base64": "<VOTRE_PDF_D_EXEMPLE_EN_BASE64>",
    "header_media_mime": "application/pdf"
  }),
});
console.log(res.status, await res.json());

PHP

$ch = curl_init("https://wa-api.geskap.com/v1/templates");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("GESKAP_API_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "name" => "facture_pdf",
    "language" => "fr",
    "category" => "UTILITY",
    "body_text" => "Bonjour {{1}}, voici votre facture {{2}}. Merci de votre confiance.",
    "example_values" => ["Awa", "F-2026-0412"],
    "header_media_base64" => "<VOTRE_PDF_D_EXEMPLE_EN_BASE64>",
    "header_media_mime" => "application/pdf",
  ]),
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;

Python

import os, requests

res = requests.request(
    "POST",
    "https://wa-api.geskap.com/v1/templates",
    headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
    json={
        "name": "facture_pdf",
        "language": "fr",
        "category": "UTILITY",
        "body_text": "Bonjour {{1}}, voici votre facture {{2}}. Merci de votre confiance.",
        "example_values": ["Awa", "F-2026-0412"],
        "header_media_base64": "<VOTRE_PDF_D_EXEMPLE_EN_BASE64>",
        "header_media_mime": "application/pdf",
    },
    timeout=15,
)
print(res.status_code, res.json())
  1. Une fois APPROVED, envoyez-le avec l'adresse du PDF de ce client :

curl

curl -X POST "https://wa-api.geskap.com/v1/messages" \
  -H "Authorization: Bearer $GESKAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "+237690000001",
  "template": {
    "name": "facture_pdf",
    "language": "fr",
    "variables": ["Awa", "F-2026-0412"],
    "header_document_url": "https://files.example.com/factures/F-2026-0412.pdf",
    "header_document_filename": "facture-F-2026-0412.pdf"
  },
  "idempotency_key": "facture-pdf-F-2026-0412"
}'

Node.js

const res = await fetch("https://wa-api.geskap.com/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "to": "+237690000001",
    "template": {
      "name": "facture_pdf",
      "language": "fr",
      "variables": ["Awa", "F-2026-0412"],
      "header_document_url": "https://files.example.com/factures/F-2026-0412.pdf",
      "header_document_filename": "facture-F-2026-0412.pdf"
    },
    "idempotency_key": "facture-pdf-F-2026-0412"
  }),
});
console.log(res.status, await res.json());

PHP

$ch = curl_init("https://wa-api.geskap.com/v1/messages");
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("GESKAP_API_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode([
    "to" => "+237690000001",
    "template" => [
      "name" => "facture_pdf",
      "language" => "fr",
      "variables" => ["Awa", "F-2026-0412"],
      "header_document_url" => "https://files.example.com/factures/F-2026-0412.pdf",
      "header_document_filename" => "facture-F-2026-0412.pdf",
    ],
    "idempotency_key" => "facture-pdf-F-2026-0412",
  ]),
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;

Python

import os, requests

res = requests.request(
    "POST",
    "https://wa-api.geskap.com/v1/messages",
    headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
    json={
        "to": "+237690000001",
        "template": {
            "name": "facture_pdf",
            "language": "fr",
            "variables": ["Awa", "F-2026-0412"],
            "header_document_url": "https://files.example.com/factures/F-2026-0412.pdf",
            "header_document_filename": "facture-F-2026-0412.pdf",
        },
        "idempotency_key": "facture-pdf-F-2026-0412",
    },
    timeout=15,
)
print(res.status_code, res.json())

Règles exactes

ÉlémentRègle
CréationPDF d'exemple 16 Mo au plus, header_media_mime obligatoire quand il y a un fichier
Types de document acceptésPDF, texte, Word, Excel, PowerPoint
Envoiheader_document_url : adresse https publique (pas de localhost, pas d'adresse privée)
Nom affichéheader_document_filename ; par défaut recu.pdf
Un seul en-têteheader_document_url ou header_image_url, jamais les deux (422)
Attention
Piège : l'exemple joint à la création n'est pas le fichier envoyé. Il sert seulement à Meta pour valider le gabarit. Le PDF réel part par URL à chaque envoi, et c'est Meta qui va le chercher : l'adresse doit être joignable depuis Internet à ce moment-là.
Attention
Piège : sans header_media_mime, la création est refusée avec un message qui demande le type : c'est lui qui décide si l'en-tête est une image, un document ou une vidéo. Mettez application/pdf.
Attention
Piège : l'adresse du PDF est lisible par quiconque reçoit le message ou la transfère. Pour une facture nominative, servez un lien difficile à deviner et de courte durée (voir Données sensibles : lien unique et PIN).

Le template se crée une fois ; seul le PDF et le nom changent d'un envoi à l'autre. Premier envoi, statuts et erreurs : Écrire à un client qui ne vous a jamais écrit. Un en-tête image suit la même mécanique : Image en en-tête.

Preuve — vérifié dans le code de l'API : api_schemas.py, api_send.py, cloud_api.py.