Entwickler

API-Dokumentation

Lege Leads per REST-API an, lass sie automatisch per WhatsApp anschreiben und erhalte Ereignisse — zum Beispiel ob eine Nummer auf WhatsApp erreichbar ist — per Webhook zurück in dein System.

Einführung

Die Public API ist eine REST-API mit JSON-Bodys. Alle Endpoints liegen unter der Base-URL:

https://comu-ai.de/api/v1

Typischer Ablauf: Dein System (Website-Formular, CRM, eigenes Backend) legt per POST /v1/leads einen Lead an. Comu.ai schreibt den Lead sofort über den gewählten Agent per WhatsApp an. Ob die Nummer WhatsApp-fähig ist, erfährst du Sekunden später per Webhook (lead.undeliverable) oder per Polling (GET /v1/leads/{id}).

Authentifizierung

Alle Requests brauchen einen API-Schlüssel als Bearer-Token. Schlüssel erstellst du in den Einstellungen unter „API“ (nur Account-Owner). Der Schlüssel wird dir genau einmal angezeigt — speichere ihn sicher, z. B. in einem Secret-Manager. Übertrage ihn nie im Frontend-Code.

curl https://comu-ai.de/api/v1/agents \
  -H "Authorization: Bearer comu_live_IhrSchluessel"

Ungültige oder widerrufene Schlüssel erhalten 401 UNAUTHORIZED.

Rate Limits

Limits gelten pro API-Schlüssel und Minute:

EndpointLimit
POST /v1/leads10 Requests / Minute (jeder Call löst einen WhatsApp-Versand aus)
GET-Endpoints60 Requests / Minute

Bei Überschreitung antwortet die API mit 429 RATE_LIMITED inklusive Retry-After-Header. Jede Antwort enthält X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset.

Lead anlegen

POST/v1/leads

Legt einen Lead an und verschickt sofort die Startnachricht (bzw. Opt-In-Frage) des Agents. Der Agent muss dafür bereit sein — seine Opener-Vorlage muss von WhatsApp freigegeben sein (siehe Agents auflisten).

FeldTypBeschreibung
agent_idstring (UUID), PflichtDer Agent, der den Lead anschreibt (siehe GET /v1/agents)
phonestring, PflichtTelefonnummer, beliebiges Format — wird zu E.164 normalisiert (deutsche Nummern ohne Ländervorwahl werden als +49 interpretiert)
namestring, PflichtName des Leads (Vorname wird in der Startnachricht verwendet)
emailstring, optionalE-Mail-Adresse
external_idstring, optionalDeine eigene ID (Idempotenz-Schlüssel, siehe unten)
custom_fieldsobject, optionalSchlüssel-Wert-Paare (z. B. Formularantworten) — stehen dem KI-Agent als Kontext zur Verfügung
curl -X POST https://comu-ai.de/api/v1/leads \
  -H "Authorization: Bearer comu_live_IhrSchluessel" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "6f1e...c2a0",
    "phone": "+49 151 23456789",
    "name": "Max Mustermann",
    "email": "max@example.org",
    "external_id": "crm-lead-4711",
    "custom_fields": { "plz": "10717", "anliegen": "Beratung Solaranlage" }
  }'

Antwort 201 Created:

{
  "lead": {
    "id": "a3d2...91bc",
    "external_id": "crm-lead-4711",
    "status": "open",
    "name": "Max Mustermann",
    "phone": "4915123456789",
    "agent_id": "6f1e...c2a0",
    "created_at": "2026-07-08T09:30:00.000Z",
    "whatsapp": { "undeliverable": false, "undeliverable_at": null },
    "qualification": { "qualified": false, "qualified_at": null, "trigger": null },
    "appointment": { "at": null },
    "opted_out_at": null,
    "fields": {}
  },
  "first_message": { "status": "sent" }
}

first_message.status ist "sent" oder "blocked" (mit reason), falls WhatsApp den Versand abgelehnt hat. Der Lead existiert auch bei "blocked" und ist im Dashboard sichtbar.

Idempotenz

Übergib eine external_id (im Body oder als Idempotency-Key-Header). Wiederholte Requests mit derselben external_id legen keinen zweiten Lead an, sondern antworten 200 mit dem bestehenden Lead und "replayed": true. Ein Lead mit bereits aktiver Telefonnummer wird unabhängig davon mit 409 DUPLICATE_LEAD abgelehnt.

Lead abfragen

GET/v1/leads/{id}

{id} ist die Comu-Lead-UUID oder deine external_id. Die Antwort enthält denselben lead-Body wie oben — inklusive whatsapp.undeliverable für Polling, falls du keine Webhooks nutzen willst, und den aktuellen fields (siehe Lead-Felder).

curl https://comu-ai.de/api/v1/leads/crm-lead-4711 \
  -H "Authorization: Bearer comu_live_IhrSchluessel"

Agents auflisten

GET/v1/agents

Liefert alle Agents des Accounts mit ihrer Sende-Bereitschaft. Nur Agents mit "ready": true können Leads entgegennehmen; sonst nennt reason die Ursache (NO_WHATSAPP, NO_TEMPLATE, TEMPLATE_NOT_APPROVED).

{
  "agents": [
    { "id": "6f1e...c2a0", "name": "Solar-Beratung", "ready": true, "reason": null }
  ]
}

Fehlercodes

Fehler kommen immer im selben Envelope:

{ "error": { "code": "TEMPLATE_NOT_APPROVED", "message": "...", "details": { } } }
HTTPCodeBedeutung
400INVALID_JSON / VALIDATION_ERRORBody fehlerhaft; details.issues nennt die Felder
400INVALID_PHONENummer konnte nicht zu E.164 normalisiert werden
401UNAUTHORIZEDAPI-Schlüssel fehlt, ist ungültig oder widerrufen
404AGENT_NOT_FOUND / LEAD_NOT_FOUNDRessource existiert nicht in diesem Account
409DUPLICATE_LEADAktiver Lead mit dieser Nummer existiert bereits (details.existing_lead_id)
422NO_WHATSAPPAgent hat kein aktives WhatsApp-Konto
422NO_TEMPLATEAgent hat keine Opener-Vorlage konfiguriert
422TEMPLATE_NOT_APPROVEDOpener-Vorlage wartet noch auf WhatsApp-Freigabe
429RATE_LIMITEDRate Limit überschritten — Retry-After beachten
500INTERNAL_ERRORUnerwarteter Fehler — Request wiederholen

Webhooks

Registriere in den Einstellungen unter „API“ eine https-URL und erhalte Lead-Ereignisse als POST mit JSON-Body. Ohne Event-Auswahl bekommst du alle Ereignisse.

EventWann
lead.createdNeuer Lead angelegt (API, Meta-Formular, Dashboard, WhatsApp-Erstkontakt)
lead.undeliverableWhatsApp meldet: Nummer nicht erreichbar (kein WhatsApp) — i. d. R. Sekunden nach dem ersten Sendeversuch
lead.respondedLead hat zum ersten Mal geantwortet
lead.qualifiedLead wurde qualifiziert (data.trigger: booking oder classifier)
lead.lostLead verloren (data.reason: opt_out, opt_in_expired oder manual)
appointment.bookedTermin gebucht oder umgebucht (data.appointment mit Zeit, Provider, Link)
lead.updatedEin Lead-Feld hat einen neuen Wert, vom Agenten im Chat erkannt oder im Dashboard gepflegt (data.changed: Liste der geänderten Feld-Keys)

Beispiel-Payload:

{
  "id": "7c04...e8d1",
  "type": "lead.undeliverable",
  "created_at": "2026-07-08T09:30:07.000Z",
  "data": {
    "lead": {
      "id": "a3d2...91bc",
      "external_id": "crm-lead-4711",
      "status": "open",
      "whatsapp": { "undeliverable": true, "undeliverable_at": "2026-07-08T09:30:06.512Z" }
    }
  }
}

Lead-Felder

Jeder lead enthält fields: die Lead-Felder, die du im Dashboard unter Leads → „Anpassen“ angelegt hast, mit ihrem aktuellen Wert. Der Agent füllt sie im Chat, dein Team kann sie im Lead pflegen. Schlüssel ist der Feld-Key, der bei einer Umbenennung gleich bleibt. Leere Felder fehlen im Objekt. Neue Felder erscheinen automatisch, ohne Änderung an der API.

Über lead.updated bekommst du jede Änderung sofort. Ordne den Lead über deine external_id zu und übernimm fields komplett, so bleibt deine Tabelle auch bei verpassten Events aktuell. Weil fehlgeschlagene Zustellungen später wiederholt werden, kann ein älteres Event nach einem neueren ankommen: Speichere created_at mit und ignoriere ältere Events, oder lade den Lead bei lead.updated per GET /v1/leads/{id} frisch. Hinweis: custom_fields beim Anlegen sind reiner Kontext für den Agenten und erscheinen nicht in fields.

{
  "id": "1b9f...44ac",
  "type": "lead.updated",
  "created_at": "2026-10-14T10:12:41.000Z",
  "data": {
    "lead": {
      "id": "a3d2...91bc",
      "external_id": "crm-lead-4711",
      "status": "responded",
      "fields": {
        "betrieb": "Bäckerei Müller",
        "werbegrund": "Neueröffnung",
        "ortsteil": "Friedenau"
      }
    },
    "changed": ["ortsteil"]
  }
}

Signatur prüfen

Jeder Request trägt einen X-Comu-Signature-Header im Format t=<unix>,v1=<hex>. Die Signatur ist ein HMAC-SHA256 über `${t}.${rawBody}` mit deinem Endpoint-Secret (whsec_…). Prüfe zusätzlich, dass t nicht älter als 5 Minuten ist (Replay-Schutz).

// Node.js
const crypto = require("crypto")

function verifyComuSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(",").map(p => p.split("=")))
  const age = Math.abs(Date.now() / 1000 - Number(parts.t))
  if (age > 300) return false // Replay-Fenster 5 min

  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex")
  return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
}

Weitere Header: X-Comu-Event (Event-Typ), X-Comu-Event-Id (stabile Event-ID für Dedup) und X-Comu-Delivery-Id (pro Zustellversuch).

Zustellung & Retries

Antworte mit einem 2xx-Status innerhalb von 10 Sekunden. Zustellung ist at-least-once: Dedupliziere über die Event-ID (id im Body bzw. X-Comu-Event-Id). Bei Fehlern versuchen wir es erneut nach 1, 5 und 30 Minuten sowie 2 und 12 Stunden (insgesamt 6 Versuche). Nach 20 fehlgeschlagenen Zustellungen in Folge wird der Endpoint automatisch deaktiviert — in den Einstellungen kannst du ihn wieder aktivieren und testen.

WhatsApp-Erreichbarkeit

Es gibt keinen offiziellen Weg, vor dem Versand zu prüfen, ob eine Nummer auf WhatsApp ist — Meta bietet dafür bewusst keine Lookup-API an. Das zuverlässige (und einzige ToS-konforme) Signal entsteht beim ersten Sendeversuch: Ist die Nummer nicht auf WhatsApp, meldet Meta den Fehler 131026 („Message undeliverable“) — asynchron, typischerweise wenige Sekunden nach dem Versand.

Comu.ai übersetzt das für dich in Echtzeit: Lead per API anlegen → Startnachricht geht raus → bei Nichterreichbarkeit kommt das Webhook-Event lead.undeliverable (bzw. whatsapp.undeliverable = true beim Polling). In der Praxis hast du das Ergebnis meist in unter 10 Sekunden nach dem API-Call. Nicht zustellbare Nachrichten berechnet WhatsApp nicht.

Hinweis: 131026 ist ein Sammel-Fehlercode — neben „kein WhatsApp“ kann in seltenen Fällen z. B. eine stark veraltete WhatsApp-Version des Empfängers die Ursache sein. Für Formular-Validierung in Echtzeit empfehlen wir zusätzlich eine clientseitige Plausibilitätsprüfung (Mobilfunknummer, E.164-Format), bevor du den Lead pushst.

Abrechnung

API-Leads durchlaufen dieselbe Pipeline wie alle anderen Leads: Abgerechnet werden ausschließlich qualifizierte Leads gemäß deinem Tarif — das Anlegen von Leads und die Webhook-Zustellung sind kostenfrei. Nicht zustellbare Nummern (lead.undeliverable) erzeugen keine Kosten.

Stand: Juli 2026 · API-Version v1 · Fragen an info@phonoflow.de