Brancher WhatsApp sur n8n, Zapier, un CRM ou un LLM (ChatGPT, Claude)

Oui : chaque message reçu arrive en POST HTTPS signé sur l'adresse de votre choix, donc n8n, Zapier, un CRM ou votre code peuvent le prendre ; pour un LLM, message.inbound puis votre code puis le modèle puis POST /v1/messages/reply.

Brancher WhatsApp sur n8n, Zapier, un CRM ou un LLM (ChatGPT, Claude)

Astuce
Oui : chaque message reçu arrive en POST HTTPS signé sur l'adresse de votre choix, donc n8n, Zapier, un CRM ou votre code peuvent le prendre ; pour un LLM, message.inbound puis votre code puis le modèle puis POST /v1/messages/reply.

Scénario

Vous ne voulez pas écrire un serveur complet. Vous voulez que chaque message reçu sur votre numéro WhatsApp alimente votre CRM, déclenche un scénario n8n ou Zapier, ou soit confié à un modèle de langage (LLM) qui prépare la réponse. Oui, c'est possible, parce que l'API ne fait que pousser un POST HTTPS : tout ce qui sait recevoir ce POST peut s'y brancher.

Le principe

Client WhatsApp
   │  écrit
   ▼
API Geskap ──POST signé (message.inbound)──► votre adresse HTTPS
                                               │  n8n, Zapier, CRM, ou votre code
                                               ▼
                                          (optionnel) LLM
                                               │
votre code ──POST /v1/messages/reply──► API Geskap ──► Client WhatsApp

Vous enregistrez une seule adresse par compte dans la console (page Connexion, Webhook, HTTPS obligatoire). Tous les événements arrivent sur cette adresse : message.inbound (le client a écrit), message.status (statut d'un de vos envois) et message.echo (un message envoyé depuis l'application WhatsApp Business du téléphone, numéros en Coexistence). Triez sur event.

n8n

Un nœud Webhook (méthode POST, adresse publique de votre instance) reçoit l'événement. Collez cette adresse dans la console. Ensuite aiguillez sur event, puis sur data.button_id si le client a touché un bouton (voir Savoir quel bouton le client a touché).

Zapier, Make et tout outil qui reçoit un POST HTTPS

Le critère est simple : l'outil doit offrir une adresse HTTPS publique qui accepte un POST JSON et répond 2xx en moins de 8 secondes. Collez cette adresse dans la console, envoyez-vous un message WhatsApp, et regardez l'événement arriver.

Un CRM

Écrivez dans votre CRM, à chaque message.inbound, la ligne from (le numéro du client), contact_name, text, type et wa_message_id. Ce dernier est votre clé de dédoublonnage. Pour reconnaître un client déjà connu, voir Reconnaître un client par son numéro.

Un LLM (ChatGPT, Claude, ou un autre modèle)

L'API n'appelle jamais un modèle à votre place : c'est votre code qui le fait. Le schéma est le même pour tous les fournisseurs.

Node.js

// Express — the webhook answers 200 first, the model works afterwards.
// verifyAndParse(req) = the signature check from the "Verify a webhook signature" recipe.
app.post("/webhooks/whatsapp", express.raw({ type: "application/json" }), (req, res) => {
  const payload = verifyAndParse(req);          // null when the signature is wrong
  if (!payload) return res.sendStatus(401);
  res.sendStatus(200);                          // one attempt, 8 s, no retry
  if (payload.event !== "message.inbound") return;   // never react to message.echo
  answerInBackground(payload.data).catch(console.error);
});

async function answerInBackground(msg) {
  const answer = await askYourModel(msg.text);  // your LLM call (any provider)
  await fetch("https://wa-api.geskap.com/v1/messages/reply", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ to: msg.from, from: msg.number, text: answer }),
  });
}

PHP

<?php
// The webhook answers 200 first, the model works afterwards.
// verify_and_parse() = the signature check from the "Verify a webhook signature" recipe.
$payload = verify_and_parse(file_get_contents("php://input"));
if (!$payload) { http_response_code(401); exit; }
http_response_code(200);                      // one attempt, 8 s, no retry
if (function_exists("fastcgi_finish_request")) { fastcgi_finish_request(); }

if ($payload["event"] === "message.inbound") {   // never react to message.echo
    $msg = $payload["data"];
    $answer = ask_your_model($msg["text"]);   // your LLM call (any provider)
    $ch = curl_init("https://wa-api.geskap.com/v1/messages/reply");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer " . getenv("GESKAP_API_KEY"),
            "Content-Type: application/json",
        ],
        CURLOPT_POSTFIELDS => json_encode([
            "to" => $msg["from"], "from" => $msg["number"], "text" => $answer,
        ]),
    ]);
    curl_exec($ch);
}

Python

# Flask — the webhook answers 200 first, the model works afterwards.
# verify_and_parse() = the signature check from the "Verify a webhook signature" recipe.
import os, threading, requests

@app.post("/webhooks/whatsapp")
def whatsapp_webhook():
    payload = verify_and_parse(request)           # aborts with 401 if the signature is wrong
    if payload["event"] == "message.inbound":     # never react to message.echo
        threading.Thread(target=answer, args=(payload["data"],), daemon=True).start()
    return "", 200                                # one attempt, 8 s, no retry

def answer(msg):
    text = ask_your_model(msg["text"])            # your LLM call (any provider)
    requests.post(
        "https://wa-api.geskap.com/v1/messages/reply",
        headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
        json={"to": msg["from"], "from": msg["number"], "text": text},
        timeout=15,
    )

curl

# What your code sends once the model has answered.
curl -X POST "https://wa-api.geskap.com/v1/messages/reply" \
  -H "Authorization: Bearer $GESKAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "+237690000001", "from": "100000000000001", "text": "Bonjour Awa, votre rendez-vous est bien noté pour demain à 9 h."}'

La réponse part par POST /v1/messages/reply avec from égal à data.number, pour répondre depuis le numéro qui a reçu :

curl

curl -X POST "https://wa-api.geskap.com/v1/messages/reply" \
  -H "Authorization: Bearer $GESKAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "+237690000001",
  "from": "100000000000001",
  "text": "Bonjour Awa, votre rendez-vous est bien noté pour demain à 9 h."
}'

Node.js

const res = await fetch("https://wa-api.geskap.com/v1/messages/reply", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "to": "+237690000001",
    "from": "100000000000001",
    "text": "Bonjour Awa, votre rendez-vous est bien noté pour demain à 9 h."
  }),
});
console.log(res.status, await res.json());

PHP

$ch = curl_init("https://wa-api.geskap.com/v1/messages/reply");
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",
    "from" => "100000000000001",
    "text" => "Bonjour Awa, votre rendez-vous est bien noté pour demain à 9 h.",
  ]),
]);
$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/reply",
    headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
    json={
        "to": "+237690000001",
        "from": "100000000000001",
        "text": "Bonjour Awa, votre rendez-vous est bien noté pour demain à 9 h.",
    },
    timeout=15,
)
print(res.status_code, res.json())

Pour répondre avec des boutons plutôt que du texte, voir Boutons de réponse rapide. Pour un cas guidé avec passage à un humain, voir FAQ avec IA puis humain.

Le connecteur Claude (MCP)

Un connecteur MCP existe à l'adresse https://mcp.geskap.com/mcp. Il donne à Claude des outils pour lister les messages reçus, répondre, envoyer un template, vérifier un statut et lire le solde. Il fonctionne à la demande : Claude agit quand vous le lui demandez. Il ne remplace pas votre webhook pour répondre automatiquement à chaque message.

Attention
Piège : la réponse libre ne part que si le client a écrit dans les dernières 24 h. Hors de cette fenêtre, l'appel répond 400 « Hors de la fenêtre de 24h » : il faut un template.
Attention
Piège : répondez au webhook avant d'appeler le modèle. Le délai est de 8 secondes et il n'y a pas de relance : un modèle lent ferait perdre l'événement. Faites le travail en arrière-plan.
Attention
Piège : ne réagissez qu'à message.inbound. Un message.echo est un message envoyé depuis le téléphone par votre équipe : y répondre avec le modèle ferait parler le bot à votre propre équipe.
Attention
Piège : si votre numéro est partagé avec un autre système et que la réponse automatique est réglée sur l'autre système, vous ne recevez pas le push de ce numéro (les messages restent lisibles dans l'API). Voir Plusieurs numéros.

Chaque réponse libre coûte, par défaut, 1 crédit pour 4 messages : voir Coût d'un message.

Preuve — vérifié dans le code de l'API : console_api.py, client_webhooks.py, public_api.py, meta_webhooks.py, mcp_server.py, workflow_bundles.py, webhooks.py.