Bau einen WhatsApp-Agent
mit OpenClaw + TimelinesAI

Mach WhatsApp zu einem Kundenkanal, den dein OpenClaw-Agent bedient: eingehende Nachrichten automatisch beantworten, transaktionale und ereignisgesteuerte Benachrichtigungen senden, mit deinem CRM synchronisieren, den Posteingang mit dem Team teilen. TimelinesAI betreibt das WhatsApp-Gateway. Dein Agent kümmert sich um die Logik.

Auto-AntwortTransaktionale SendsCRM-SyncGeteilter Posteingang

Was du bauen kannst

Was dein Agent mit WhatsApp machen kann

Mit einem TimelinesAI-Skill installiert kann dein OpenClaw-Agent WhatsApp als echten Kundenkanal bedienen. Die lange Version dieser Liste steht in den Abschnitten unten — jede Fähigkeit hat die spezifischen API-Calls, die sie braucht.

Eingehende Kundennachrichten automatisch beantworten — 24/7-Autoresponder, Out-of-Office-Antwort oder ein vollwertiger AI-Chatbot, der bei Bedarf an einen Menschen übergibt.

Transaktionale und ereignisgesteuerte Nachrichten senden — Bestellbestätigungen, Versand-Updates, Terminerinnerungen, Zahlungsbestätigungen und Benachrichtigungen, ausgelöst von Events in anderen Tools (HubSpot, Stripe, Calendly). Nur an Kunden, die die Nachricht erwarten.

Leads in mehrteiligen Gesprächen qualifizieren — eine Reihe von Fragen stellen, Antworten speichern, den Chat als qualifiziert markieren oder nicht.

WhatsApp-Aktivität mit deinem CRM synchronisieren — eingehende Nummern in HubSpot/Pipedrive nachschlagen, Deal-Stufen aktualisieren, Notizen zurückschreiben.

Gespräche zusammenfassen und bewerten — „Was hat ACME letzte Woche gefragt?“, „Bewerte diesen Chat 1–10 nach Intent“.

Den Posteingang mit dem Team teilen — dein Agent entwirft Antworten als private Notizen, Menschen senden sie ab; oder der Agent sendet und Menschen schauen zu.

Mit Medien umgehen — Belege als Fotos, PDFs, Sprachnachrichten — von OpenClaw verarbeitet und automatisch beantwortet.


Start hier

Wie du diesen Guide benutzt

Dieser Guide ist dual-mode. Du kannst ihn selbst lesen und das Setup manuell durchgehen — der Weg, den die Sections unten auslegen — oder du gibst das Ganze deinem OpenClaw-Agent (oder irgendeinem Agent, der eine URL lesen kann) und lässt ihn das Setup für dich machen. Beide Wege landen am selben Ort: vier WhatsApp-Skills installiert, Token und Webhook verdrahtet, dein Agent antwortet auf Kundennachrichten.

Wenn du das selbst liest

Scroll durch die Capability-Gruppen unten — Incoming, Outbound, CRM & Analytics, Operations — und entscheide, welche für deinen Use Case zählen. Dann folge dem Setup manuell; vier Schritte, etwa zehn Minuten. Jede Capability listet die genauen API-Calls, die sie macht, und jeder Code-Block in diesem Guide wurde vor der Veröffentlichung gegen die echte TimelinesAI-API ausgeführt. Wenn du stecken bleibst, sammelt Wichtig zu wissen jedes Gotcha, das dich sonst eine Stunde kostet.

Wenn dein Agent das Setup übernehmen soll

Paste diesen Prompt in OpenClaw (oder Claude Code, Cursor, Claude Desktop, irgendeinen Agent, der eine URL lesen kann). Er zeigt per URL auf diesen Guide, damit der Agent die live-Version liest, und führt dich dann durch Install, Token, Webhook, Smoke-Test und das Aktivieren deines ersten Skills — und fragt dich nur dann nach Input, wenn er wirklich was von dir braucht.

Read https://timelines.ai/guide/openclaw-whatsapp-skills end to end.

Then help me set up an OpenClaw + TimelinesAI WhatsApp agent:

1. Clone InitechSoftware/openclaw-whatsapp-skills into ~/.openclaw/workspace/
   and symlink the four skills into ~/.openclaw/skills/.

2. Ask me for my TimelinesAI API token (Integrations → Public API → Copy
   on app.timelines.ai) and write it to ~/.openclaw/workspace/.env.timelinesai
   as TIMELINES_AI_API_KEY.

3. Run the smoke-test curl from Setup. If it fails, walk me through the
   "If you see something else" failure block in the guide.

4. Help me deploy examples/vercel-webhook-receiver from the companion repo
   and register the webhook with TimelinesAI.

5. Ask which of the four skills I want to enable first — whatsapp-autoresponder,
   whatsapp-lead-qualifier, whatsapp-send, or whatsapp-delivery-check — and
   walk me through its capability section plus any relevant gotchas from
   Things-to-know.

6. Before enabling anything that sends outbound messages, show me Channel
   choice so I pick the right WhatsApp channel (personal number vs Business
   API) for my use case.

Der Prompt hört absichtlich auf, bevor irgendwas Outbound anschaltet — er routet dich erst durch Channel choice, damit du den richtigen WhatsApp-Kanal (persönliche Nummer vs Business API) für das wählst, was du tatsächlich vorhast. Das Ban-Risiko ist echt und liegt upstream, nicht etwas, wovor dich das Gateway schützen kann.


Einstieg

Setup

Vier Dinge zum Verdrahten. Danach macht dein Skill nur noch API-Calls.

  1. 1

    Verbinde deine WhatsApp-Nummer

    Bei app.timelines.ai anmelden, den QR-Code mit dem Telefon scannen, das deine Geschäftsnummer hält. TimelinesAI betreibt das Gateway ab hier. Derselbe Ablauf wie das normale Personal-Number-Setup.

  2. 2

    Hol dir ein API-Token

    Integrationen → Public API → Kopieren. Speichere als TIMELINES_AI_API_KEY. Ein Token deckt den ganzen Workspace ab.

  3. 3

    Installiere einen Skill aus dem Companion-Repo

    Clone InitechSoftware/openclaw-whatsapp-skills nach ~/.openclaw/workspace/, dann symlinke jedes Skill-Verzeichnis nach ~/.openclaw/skills/. Vier-Zeilen-Quickstart im Companion-README.

  4. 4

    Registriere einen Webhook

    Zeig message:received:new auf eine öffentliche HTTPS-URL. TimelinesAI pusht eingehende Nachrichten dorthin. Dein Receiver ruft OpenClaw auf.

Token-Smoketest

Bevor du irgendwas baust, bestätige, dass die Auth funktioniert:

curl -sS -w "\nHTTP: %{http_code}\n" \
  -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
  https://app.timelines.ai/integrations/api/whatsapp_accounts

Du solltest sehen

{
  "status": "ok",
  "data": {
    "whatsapp_accounts": [
      {
        "id": "<phone>@s.whatsapp.net",
        "phone": "+<phone>",
        "status": "connected",
        "account_name": "<your label>"
      }
    ]
  }
}

Wenn du etwas anderes siehst

  • HTTP 401 mit dem reinen Text Unauthorized — das Token ist nicht angekommen. Prüf Schritt 2 nochmal, stell sicher dass der Bearer-Prefix dabei ist und dass das Token beim Einfügen nicht umgebrochen wurde. Die Antwort ist reiner Text, kein JSON — ein Pipe zu jq wird crashen.
  • Eine bootstrap-gestylte HTML-„Page not found“-Seite mit HTTP 404 — du hast einen nachgestellten Slash oder einen Tippfehler im Pfad. Die API liefert HTML (keinen JSON-Fehler) für falsche Pfade — wenn deine Ausgabe mit <!DOCTYPE html> beginnt, entfern jeden trailing Slash und prüf den Pfad.

Webhook einmal registrieren

# Generate a random secret once. Add it as a query param so only
# TimelinesAI's registered URL can reach your receiver.
WEBHOOK_SECRET=$(openssl rand -hex 16)
WEBHOOK_URL="https://your-app.example.com/api/webhook?secret=${WEBHOOK_SECRET}"

curl -sS -X POST \
  -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"event_type\":\"message:received:new\",\"url\":\"${WEBHOOK_URL}\",\"enabled\":true}" \
  https://app.timelines.ai/integrations/api/webhooks

Minimaler Webhook-Receiver (Node / Vercel)

~40 Zeilen. Prüft den ?secret= Query-Parameter, wirft ausgehende Echos raus, acks 2xx innerhalb des 5-Sekunden-Retry-Fensters und reicht die Nachricht dann an deinen OpenClaw-Host weiter.

// api/webhook.js
export default async function handler(req, res) {
  // Path-segment auth — TimelinesAI doesn't sign webhooks yet, so the
  // ?secret=<token> you registered is the only thing stopping strangers
  // from invoking this endpoint.
  if (req.query.secret !== process.env.WEBHOOK_SECRET) {
    return res.status(404).end();
  }
  if (req.method !== "POST") return res.status(405).end();

  const { event_type, data } = req.body || {};
  if (event_type !== "message:received:new") {
    return res.status(200).json({ ignored: event_type });
  }

  // Drop your agent's own sends echoing back as "received".
  // TimelinesAI also fires message:received:new for outbound messages
  // synced from another WhatsApp client on the same number.
  if (data.from_me === true) {
    return res.status(200).json({ ignored: "from_me" });
  }

  // Ack FAST. TimelinesAI retries 3x with a 5-second timeout per attempt —
  // if you take longer than 5s the same event hits you up to 3 times.
  res.status(200).json({ ok: true });

  // TL webhook payloads sometimes arrive flat (data.chat_id) and sometimes
  // nested (data.chat.id) depending on event flavor and version. Destructure
  // with fallback so both shapes work — see Things to know below.
  const chatId = data.chat_id ?? data.chat?.id;
  const whatsappAccountId =
    data.whatsapp_account_id ?? data.chat?.whatsapp_account_id;

  // Fire-and-forget handoff to wherever your OpenClaw host accepts
  // inbound messages. For production durability replace this with a
  // push to a queue (QStash, Inngest, SQS) your agent drains separately.
  try {
    await fetch(process.env.OPENCLAW_HOOK_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        chat_id: chatId,
        whatsapp_account_id: whatsappAccountId,
        text: data.text,
        sender_phone: data.sender_phone,
        message_uid: data.message_uid,
      }),
    });
  } catch (err) {
    console.error("[timelinesai-webhook] handoff failed:", err);
  }
}

OPENCLAW_HOOK_URL ist der Endpoint, den dein OpenClaw-Deployment für eingehende Nachrichten bereitstellt — hängt davon ab, wie du den Agent hostest. Für eine produktionsreife Referenz mit Durability-Notizen siehe examples/vercel-webhook-receiver im Companion-Repo.


Dein Install

Was in deinem OpenClaw-Workspace drin ist

Du hast einen TimelinesAI-Token verbunden und Skills aus dem Companion-Repo gesymlinkt. Hier ist, was du tatsächlich installiert hast, und welche Dateien du editierst, wenn du ändern willst, wie dein Agent denkt, was er weiß und wie er sich verhält. Nichts hier ist Magie — es ist alles einfaches Markdown und Env-Dateien in zwei Verzeichnissen.

Skills — ~/.openclaw/skills/<skill>/

Jeder Skill, den du gesymlinkt hast, ist ein Verzeichnis mit einer Pflichtdatei (SKILL.md) plus den Ressourcen, auf die diese Datei verweist. Wenn du ändern willst, wie dein FAQ-Handler klingt oder die Reihenfolge der Fragen des Lead-Qualifiers umsortieren, lebt die Änderung im SKILL.md dieses Skills. Kein Code zum Neukompilieren, kein Build-Step.

DateiWas sie steuert
SKILL.mdDer ganze Skill. YAML-Frontmatter oben (name, description, user-invocable und etwaige Dispatch-Flags) plus der Body mit Anweisungen/Prompt darunter. Hier editierst du, was der Skill tut, wann OpenClaw ihn wählt und wie er sich verhält. Jeder der vier WhatsApp-Skills im Companion-Repo ist ein einzelnes SKILL.md — kein Code zum Neukompilieren, einfach editieren und neu starten.
README.mdMenschliche Dokumentation für den Skill. OpenClaw liest diese Datei NICHT — sie ist für dich, dein Team und jeden, der den Skill auf GitHub ansieht. Optional; die Skills im Companion-Repo haben alle eine.
Scripts, resources, schemasAlles, was im SKILL.md über {baseDir}/... referenziert wird — Helper-Skripte, Prompt-Templates, JSON-Schemas, Test-Fixtures. Von OpenClaw ignoriert, wenn dein SKILL.md nicht explizit darauf zeigt. Leg was der Skill braucht neben SKILL.md und referenzier es per Pfad.

Für die vier WhatsApp-Skills — whatsapp-autoresponder, whatsapp-lead-qualifier, whatsapp-send, whatsapp-delivery-check — hat der Kopf jeder SKILL.md die Role und die erlaubten Tools; der Body hat den echten Prompt, mit dem dein Agent läuft. Lies eines davon end-to-end, bevor du editierst; die Muster wiederholen sich.

Workspace — ~/.openclaw/workspace/

Das Workspace-Verzeichnis ist OpenClaws Zuhause für alles, was kein Skill ist: wer dein Agent ist, was er über dich weiß, welche Tools er hat, wie er bootet. OpenClaws eigene Docs sagen „dieser Ordner ist Zuhause. Behandle ihn so“. Diese Dateien kommen mit dem Install — du füllst sie mit der Zeit aus:

DateiWas sie steuert
IDENTITY.mdWer dein Agent ist — Name, Creature (wie er sich selbst konzeptualisiert), Vibe, Signatur-Emoji, Avatar. Füll das früh aus; die meisten anderen Dateien verweisen darauf.
SOUL.mdKern-Betriebsprinzipien und Persönlichkeit. Wie der Agent sich verhalten soll, wenn niemand hinsieht — wann echt vs performativ, wie man Vertrauen verdient, wo Privatsphären-Grenzen liegen. OpenClaws Docs nennen es das Gewissen des Agents.
USER.mdWem der Agent hilft (dir). Name, Pronomen, Zeitzone, Interessen, Projektkontext. Agents sind besser darin, einer spezifischen Person zu helfen als einem generischen „user“ — diese Datei ist wie sie sich erinnern.
TOOLS.mdUmgebungsspezifische Config, die du in keinem Skill haben willst — Gerätenamen, Host-Adressen, lokale Präferenzen. Lebt im Workspace, damit du sie editieren kannst, ohne etwas zu berühren, was du teilen könntest.
AGENTS.mdDas Workspace-README für Agents. Beschreibt, wie OpenClaw erwartet, dass dieser Workspace betrieben wird. Kommt normalerweise mit dem Install und wird selten editiert.
BOOT.mdKurze, explizite Anweisungen, was OpenClaw beim Start tun soll. Leer per Default; füll es aus, wenn du deterministisches Boot-Verhalten über Neustarts hinweg willst.
BOOTSTRAP.mdFirst-Boot-Onboarding-Konversation. Führt einen frischen Workspace durch das Aufsetzen seiner Identität und weist dich dann an, die Ergebnisse in IDENTITY.md, SOUL.md, USER.md zu speichern. Lösch es, sobald du die anderen ausgefüllt hast.
HEARTBEAT.mdPeriodische Task-Definitionen. Leere Datei bedeutet keine Heartbeats; füge hier Tasks hinzu, wenn du willst, dass der Agent etwas in Intervallen prüft (z.B. eine Queue alle fünf Minuten pollen).

Env-Dateien pro Integration

Neben den kanonischen Dateien oben fügst du eine Env-Datei pro Integration hinzu, die du verbindest. Die sind NICHT Teil des OpenClaw-Installs — sie gehören dir und halten Secrets, die nie in ein öffentliches Repo committed werden sollten:

DateiWas sie steuert
.env.<integration>Secrets und Config für eine Integration, eine Datei pro Integration. Für die WhatsApp-Arbeit hast du .env.timelinesai mit TIMELINES_AI_API_KEY und ALLOWED_SENDER_JID in Setup-Schritt 2 erstellt. Zieh dieses Muster nach, wenn du HubSpot, Stripe, Pipedrive oder irgendein anderes Tool hinzufügst — eine .env.<name> und source sie im Skill, der sie braucht.
Die Ein-Zeilen-Regel: Änder, wie dein Agent sich VERHÄLT, indem du den SKILL.md eines Skills editierst; änder, WER dein Agent ist, indem du SOUL.md oder IDENTITY.md im Workspace editierst; änder, welche SECRETS er benutzt, indem du die richtige .env.<integration>-Datei editierst. Alles andere im Workspace ist Maschinerie, die du selten anfasst.

Kanalwahl

Personal Numbers vs WhatsApp Business API

Bevor du Outbound-Flows baust, versteh, zu welchem WhatsApp-Kanal dein Use Case gehört. Falsch wählen führt zu Sperren.

In diesem Guide geht es um persönliche WhatsApp-Nummern — die, die du per QR-Code mit TimelinesAI verbindest. Sie sind für eingehende Gespräche und transaktionale Sends an Kunden, die die Nachricht erwarten. Für Cold Outreach, Marketing-Broadcasts oder Werbekampagnen nimm stattdessen WhatsApp Business API — TimelinesAI unterstützt das heute über das Dashboard, und Public-API-Automatisierung für Business-API-Workflows kommt in Q2 2026.

Sicher mit den Skills in diesem Guide (Personal Number)

  • Eingehende Gespräche. Der Kunde schreibt zuerst, du antwortest. Kein Risiko.
  • Transaktionale Sends. Bestellbestätigungen, Versand-Updates, Liefer-Benachrichtigungen, Zahlungsbestätigungen, Terminerinnerungen — jede Nachricht, die ein Kunde erwartet, weil er gerade etwas mit deinem Business gemacht hat.
  • Ereignisgesteuerte Benachrichtigungen aus anderen Tools. HubSpot-Deal → Demo-Bestätigung, Stripe-Zahlungsfehler → Recovery-Notiz, Calendly-Buchung → Pre-Meeting-Reminder. Der Kunde hat opted in, als er das Upstream-Tool benutzt hat.
  • Antworten innerhalb des 24-Stunden-Customer-Service-Fensters von WhatsApp. Sobald ein Kunde dir schreibt, hast du 24 Stunden Zeit, frei zu antworten. Die Autoresponder-, FAQ-Handler- und Lead-Qualifier-Skills arbeiten innerhalb dieses Fensters.

NICHT sicher auf Personal Numbers

  • Cold Outreach an Listen, die du gekauft oder gescraped hast — WhatsApp sperrt Nummern dafür innerhalb von Stunden.
  • Marketing-Broadcasts an Kunden, die nicht explizit opted in sind.
  • Werbekampagnen, Verkaufsangebote, saisonale Pushs, Produktlaunches.
  • Alles, was nach einem Marketing-Blast aussieht. Wenn du fragst „welchen Throughput kann ich rausholen“, bist du im falschen Kanal.

Wie du entscheidest, welchen Kanal du brauchst

  1. 1.Hat der Kunde dir zuerst geschrieben, oder antwortest du innerhalb einer aktiven 24-Stunden-Session? → Personal Number, dieser Guide.
  2. 2.Steht der Kunde kurz davor, etwas zu erhalten, das er explizit erwartet (Bestellung, Termin, Zahlung, Lieferung)? → Personal Number, transaktionaler Send.
  3. 3.Ausgelöst durch ein Customer-opt-in-Event in deinem CRM oder Billing-Tool? → Personal Number, ereignisgesteuerter Send.
  4. 4.Broadcast, Cold Outreach oder Werbekampagne? → Business API, nimm das TimelinesAI-Dashboard.
  5. 5.Nicht sicher? → Nicht senden. Behandle es als werblich und route es auf den Business-API-Pfad.

Jede Outbound-Fähigkeit unten geht davon aus, dass du diesen Test bestanden hast. Wenn du unsicher bist, lies diesen Abschnitt vor dem Shipping nochmal. TimelinesAI kann dich nicht vor der Sperre auf WhatsApp-Ebene schützen — die Sperre wird upstream durchgesetzt, in der WhatsApp-Infrastruktur, nicht im Gateway.


Referenz

API-Referenz

Jeder Endpoint, den du brauchst, um jede Fähigkeit unten zu bauen. Base URL https://app.timelines.ai/integrations/api. Auth Authorization: Bearer $TIMELINES_AI_API_KEY.

Lesen

MethodePfadWas es zurückgibt
GET/whatsapp_accountsDeine verbundenen WhatsApp-Nummern, jede mit JID, Telefon, Status und Account-Name.
GET/chatsChat-Liste. Unterstützt Filter ?phone=... und ?label=.... Paginiere mit ?page=N (50 pro Seite, fix). Jeder Chat hat whatsapp_account_id mit der JID der besitzenden Nummer und chatgpt_autoresponse_enabled — siehe Wichtig zu wissen bevor du deinen eigenen Agent launchst.
GET/chats/{id}Vollständiges Detail eines Chats.
GET/chats/{id}/messagesNachrichtenverlauf, 50 pro Seite. Paginiere mit ?page=N und schau auf data.has_more_pages. Jede Nachricht trägt from_me, sender_phone, text, timestamp, message_type, status (Sent/Delivered/Read) und origin (Public API vs WhatsApp-App).
GET/chats/{id}/labelsLabels des Chats.
GET/messages/{uid}/status_historySent / Delivered / Read-Timeline einer ausgehenden Nachricht.
GET/messages/{uid}/reactionsReaktionen auf eine Nachricht. Liefert {data: {users: [{name, phone, reaction, current}], reactions: {<emoji>: count}, total: N}} — ein Objekt, kein flaches Array. users listet, wer reagiert hat (mit dem gewählten Emoji und einem current-Boolean, das deinen eigenen Workspace markiert); reactions ist ein Histogramm nach Emoji. Leerzustand: {users: [], reactions: {}, total: 0}.
GET/filesDateien, die du über die API hochgeladen hast.
GET/webhooksDeine registrierten Webhook-Subscriptions.

Schreiben

MethodePfadWas es macht
POST/messagesSendet an eine Telefonnummer. Body: {"phone":"+...","text":"..."}. Liefert {"message_uid":"..."}.
POST/chats/{id}/messagesSendet in einen bestehenden Chat. Body: {"text":"..."}. Sender ist die WhatsApp-Nummer, der der Chat gehört.
POST/chats/{id}/notesHängt eine private Notiz an einen Chat. Wird nicht an WhatsApp gesendet, nur innerhalb von TimelinesAI sichtbar. Wird für Agent-State und Review-Workflows verwendet.
POST/chats/{id}/labelsFügt einem Chat ein Label hinzu. Für Stage-Tracking, Routing, Stop-Reply-Flags.
PATCH/chats/{id}Aktualisiert Chat-Metadaten — Verantwortlicher (responsible_email), Lesestatus und chatgpt_autoresponse_enabled. Das letzte abschalten, bevor dein Agent zu antworten anfängt, sonst rennt TLs eingebauter Responder gegen deinen.
PATCH/messages/{uid}/reactionsSetzt ein Reaktions-Emoji auf einer Nachricht. Body nimmt das reale Emoji-Zeichen, keinen Shortcode.
POST/files_uploadLädt eine Datei per multipart/form-data hoch (Feld: file). Gibt data.uid zurück, das du an chat/messages als file_uid weitergibst. Keine Upload-per-URL-Variante.
POST/webhooksRegistriert eine Webhook-Subscription.
PUT/webhooks/{id}Aktualisiert oder aktiviert/deaktiviert eine Subscription.
DELETE/webhooks/{id}Entfernt eine Subscription.

Bevor du startest

Wichtig zu wissen

Ein paar Details, die in der Referenz leicht zu übersehen sind und dich jeweils eine Stunde kosten, wenn du sie nicht kennst.

!
Keine Trailing Slashes. GET /chats funktioniert. GET /chats/ liefert die TimelinesAI-404-Seite — die wie ein Netzwerkproblem aussieht und keines ist. Jede URL in diesem Guide ist absichtlich ohne Trailing Slash geschrieben.
!
JSON-Bodies müssen gültiges UTF-8 sein — und die Falle reicht weiter als nur Heredocs. Der Parser lehnt alles andere ab. Em-Dashes und typografische Anführungszeichen sind der häufigste Auslöser, aber JEDE Shell, die in einem Nicht-UTF-8-Locale läuft (Git Bash unter Windows, einige Docker-Basis-Images, alte SSH-Sessions), zerstört diese Zeichen sowohl in Heredocs ALS AUCH in inline curl -d "..."-Argumenten — das Em-Dash wird zu Byte 0x97, was auf dem Server am UTF-8-Decode scheitert. Schreib das Payload in eine Datei, die explizit als UTF-8 gespeichert ist, und nutze curl --data-binary @file.json; verlass dich nicht auf inline -d für alles mit typografischer Zeichensetzung.
!
Die Base URL ist https://app.timelines.ai/integrations/api. Manche älteren Blogposts referenzieren eine andere Subdomain mit X-API-KEY-Header — das ist veraltet. Nutze Bearer-Auth auf app.timelines.ai/integrations/api.
!
Anhang-URLs in Webhook-Payloads laufen schnell ab. Wenn ein Kunde ein Foto oder PDF schickt, lade es inline im Webhook-Handler herunter, nicht aus einem asynchronen Worker.
!
Personal Numbers werden bei Cold Outreach gesperrt. Die Send-Endpoints in diesem Guide lassen dich an jeden senden, aber WhatsApp wird deine Nummer schnell sperren für unaufgefordertes Outbound. Sende Outbound nur an Leute, die dir zuerst geschrieben haben oder die explizit eine transaktionale Nachricht erwarten. Für Broadcasts nimm WhatsApp Business API — siehe Kanalwahl.
!
Senden ist asynchron. POST /messages liefert eine message_uid — das ist eine Quittung, keine Zustellbestätigung. Nutze GET /messages/{uid}/status_history, um die tatsächliche Zustellung zu prüfen.
!
Response-Strukturen variieren — List-Endpoints verschachteln unter einem typisierten Key. Die meisten List-Endpoints liefern {"data":{"<typisierter-key>":[...]}}, nicht ein flaches {"data":[...]}. GET /whatsapp_accounts verschachtelt unter data.whatsapp_accounts, GET /chats unter data.chats (mit data.has_more_pages als Pagination-Flag), GET /chats/{id}/messages unter data.messages, GET /chats/{id}/labels unter data.labels. Ausnahmen: GET /messages/{uid}/status_history und GET /files liefern data als flaches Array. Code, der eine einheitliche Struktur annimmt, bricht beim ersten Mismatch — lies als res?.data?.<typisiert> ?? res?.data ?? [].
!
Nutze from_me, nicht sender_phone, für die Richtung beim Lesen der Historie. GET /chats/{id}/messages liefert ausgehende Nachrichten (deine eigenen Sends via Public API) mit sender_phone gesetzt auf die Team-Nummer, nicht leerer String. Eine Richtungserkennung über sender_phone != "" markiert jeden Outbound als Inbound und kehrt die Konversation um. Vertraue dem from_me-Boolean — true ist ausgehend (dein Agent spricht), false ist eingehend (Kunde).
!
In der Historie ist das Feld uid; im Webhook message_uid. Gleicher Wert, anderer Key. Wenn TL ein message:received:new-Webhook liefert, nutzt das Payload message_uid. Wenn du die Historie desselben Chats via GET /chats/{id}/messages liest, heißt das Feld uid. Es ist derselbe Identifier. Code, der UIDs von Webhooks speichert und sie später in der Historie nachschlägt, muss beide Namen normalisieren. Historien-Messages nutzen außerdem timestamp, nicht created_at.
!
Labels sind REPLACE, nicht ADD. POST /chats/{id}/labels nimmt {"labels":["needs-human","intent/sales"]} — ein Array, pluraler Key. Der Call ersetzt das komplette Label-Set am Chat; um ein Label hinzuzufügen, holst du zuerst per GET das bestehende Set, fügst deins dazu und POSTest die kombinierte Liste. Zum Löschen aller Labels: POST {"labels":[]}. Es gibt keinen funktionierenden DELETE /chats/{id}/labels/{name}-Endpoint für Einzel-Removals — du musst das neue Set ohne das gewünschte Label POSTen.
!
Frisch registrierte Webhooks können History replayen. Bei den ersten Events nach POST /webhooks kann TL eine Salve an vergangenen message:received:new-Events liefern — oder der Registrierungs-Handshake versucht es mehrfach gegen einen noch kaltstartenden Container. In beiden Fällen muss dein Receiver ab Event #1 idempotent sein. Dedupe ab dem Moment auf message_uid, nimm kein „sauberes Slate“ zum Subscribe-Zeitpunkt an.
!
Webhook-Payloads können flach ODER verschachtelt ankommen. TL liefert manchmal data.chat_id / data.whatsapp_account_id auf oberster Ebene und manchmal verschachtelt als data.chat.id / data.chat.whatsapp_account_id — die Variation hängt vom Event-Typ und der Version ab. Destrukturiere defensiv: const chatId = data.chat_id ?? data.chat?.id. Attachments sind schlimmer: data.attachment_url, data.attachment?.url und data.file_url sind alle möglich. Ein Receiver, der nur die flache Form liest, scheitert still bei verschachtelten Lieferungen.
!
Listen-Endpoints liefern fest 50 Elemente pro Seite. ?limit=N, ?per_page=N und ?page_size=N werden stillschweigend ignoriert — die API wählt ihre eigene Seitengröße. Paginiere mit ?page=N (1-indexiert) und stoppe, wenn data.has_more_pages false ist. Verifiziert auf /chats und /chats/{id}/messages; das Muster gilt für andere List-Reads. Wenn du „alle Nachrichten seit X“ brauchst, plane Seitendurchlauf ein, keinen einzigen großen limit=1000-Call.
!
Deaktiviere TimelinesAIs eingebauten ChatGPT-Autoresponder, bevor dein Agent übernimmt. Jeder Chat hat per Default chatgpt_autoresponse_enabled: true und der eingebaute TL-Responder antwortet auf eingehende Nachrichten parallel zu deinem Skill — Ergebnis: Doppelantworten, die der Kunde sieht. PATCH /chats/{id} mit {"chatgpt_autoresponse_enabled": false} schaltet es pro Chat ab. Es gibt kein Bulk-Disable in der Public API, also mach das zur ersten Aktion, die dein Receiver auf jedem Chat ausführt, den er gerade übernimmt (und einmal auf jedem Chat, den dein Agent bereits betreut).
!
Notes leben in /chats/{id}/messages mit message_type=note und from_me=false, immer. POST /chats/{id}/notes schreibt in dieselbe Timeline wie echte WhatsApp-Nachrichten — wenn du die History liest, kommen Notes mit from_me: false zurück, unabhängig davon wer sie geschrieben hat. Jeder Code, der über den Gesprächsverlauf schlussfolgert, MUSS auf .message_type == "whatsapp" filtern: eine naive Response-Time-Berechnung, die eingehend→ausgehend nur auf from_me paart, wertet jede Note als eingehende Kundennachricht und korrumpiert die Latency. Ein Summary- oder Scoring-Skill, der die komplette History an ein LLM übergibt, sieht seine eigenen früheren Notes als Kundenaussagen. Behalte Notes im Stream nur, wenn du explizit strukturierten Zustand willst (das Qualifier-Pattern); filtere sie überall sonst raus.

Wenn API-Calls fehlschlagen

Fünf Fehlermodi werden deinen Skill irgendwann treffen. Jeder sieht in der Antwort anders aus und jeder hat eine andere richtige Reaktion.

!
400 — ungültiger Request-Body. Die häufigste Ursache sind Nicht-UTF-8-Zeichen in deinem JSON (Em-Dashes, typografische Anführungszeichen aus einem Shell-Heredoc). Schreib das Payload mit expliziter UTF-8-Codierung in eine Datei und nutze curl --data-binary @file.json. Gleiche Wurzel wie der UTF-8-Tipp oben.
!
401 — Token abgelaufen oder widerrufen. Rotiere via app.timelines.ai → Integrationen → Public API → Regenerate. Das neue Token wird sofort gültig; das alte hört im selben Moment auf. Lies es aus dem Environment, damit du ohne Redeploy rotieren kannst.
!
429 — Rate Limited. Die Antwort trägt einen Retry-After-Header in Sekunden. Respektier ihn und versuch es einmal erneut. Wenn du wiederholt 429 triffst, sendet dein Skill schneller als der ~30-Nachrichten-pro-Minute-Throttle der Personal Number erlaubt — client-seitig drosseln oder Outbound-Last auf mehrere verbundene Nummern verteilen.
!
5xx — Upstream-Gateway-Problem. Mit exponentiellem Backoff retry: 2s, 4s, 8s. Nach dem dritten Fehler an einen Menschen eskalieren und aufhören zu senden. Keine Endlosschleife — anhaltendes 5xx ist meist ein Status-Page-Event von TimelinesAI, nichts, was deine Retries beheben.
!
Webhook-Idempotenz. TimelinesAI retried jede Zustellung bis zu 3-mal mit 5 Sekunden Timeout. Wenn dein Receiver nicht dedupliziert, sieht der Kunde dieselbe Antwort 3-mal. Speicher die eingehende message_uid als Chat-Notiz, bevor du verarbeitest; kommt der nächste Webhook mit derselben uid, überspringen. Notizen sind billig und für Menschen beim Debuggen sichtbar.

Ein kleines Shell-Muster, das auf jeden Status-Code verzweigt:

$ # Capture the HTTP status into a variable, then branch
$ http_code=$(curl -sS -o /tmp/resp.json -w "%{http_code}" \
    -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/send.json \
    https://app.timelines.ai/integrations/api/messages)

$ case "$http_code" in
    200|201) ;;                                # ok, carry on
    400)     echo "bad body: $(cat /tmp/resp.json)"; exit 1 ;;
    401)     echo "token expired - rotate"; exit 2 ;;
    429)     sleep 30; retry_once ;;           # respect Retry-After
    5*)      sleep $((2 ** attempt)); retry_with_backoff ;;
  esac

Sicher in Produktion betreiben

Zwei weitere Dinge, die keine Fehler sind, dir aber später einen schlechten Tag ersparen.

!
Einen still verlorenen Webhook debuggen. Wenn dein Skill in einem echten Gespräch nicht mehr antwortet, prüf in dieser Reihenfolge: (1) das Webhook-Delivery-Log von TimelinesAI — wenn das Event nicht mal deine URL erreicht, ist das Problem Registration oder DNS, nicht dein Skill; (2) die Function-Logs deiner Hosting-Plattform (Vercel-Logs etc.) auf eine unbehandelte Exception; (3) leg eine Breadcrumb-Notiz im Chat ab mit POST /chats/{id}/notes, bevor du das Event verarbeitest, damit du bestätigen kannst, dass der Receiver wirklich gefeuert hat; (4) GET /webhooks, um zu prüfen, dass die Subscription noch aktiv ist (rotierte Tokens lassen Subscriptions gelegentlich still ausfallen).
!
Ein Token deckt den ganzen Workspace ab. Dein API-Token liest und schreibt jeden Chat, jede Nachricht, jedes Label, jede Notiz, jede Datei und jeden Webhook im Workspace. Behandle es wie ein Passwort: in .env speichern, nie commiten, und via Integrationen → Public API → Regenerate rotieren, wenn jemand das Team verlässt oder ein Deploy-Environment wechselt. Das neue Token wird sofort gültig; das alte hört im selben Moment auf. Heute gibt es keinen Per-Skill- oder Read-only-Scope.

Kontext & Gedächtnis

Gesprächskontext

Das Webhook liefert deinem Skill eine einzelne Nachricht — die gerade eingetroffene. Keine vorherigen Turns, kein Chat-Verlauf, kein laufendes Transkript. Für ein zustandsloses FAQ oder einen After-Hours-Responder reicht das völlig. Für einen Gesprächsagent, der sich daran erinnern muss, was der Kunde drei Turns zuvor gesagt hat, muss dein Skill den Kontext selbst holen. Drei Muster decken die gesamte Spannweite ab — vom billigsten möglichen Loop bis zu dem, der wirklich erinnert.

1

Zustandslos

Antworte nur auf die letzte Nachricht. Ein POST pro Turn, kein GET, kein Verlauf. Das ist der einfachste Loop und der Default in den fertigen Skills. Nutze ihn für FAQ-Bots, After-Hours-Responder und Klassifikation oder Routing — überall, wo die Antwort nur von der aktuellen Nachricht abhängt. Der Agent vergisst alles zwischen den Turns: wenn der Kunde ein Follow-up schreibt, das sich auf etwas Früheres bezieht, wird er es nicht verstehen.

2

Volles Kontextfenster

Hol die letzten 20 Nachrichten vor jeder Antwort und gib sie dem Modell als bisheriges Gespräch mit. Zwei API-Calls pro Turn statt einem, plus die zusätzlichen Prompt-Token bei jedem Call. Nutze das für Gesprächsagents, Multi-Turn-Qualifizierung und alles, wo der Agent den Faden behalten muss. Zwanzig jüngste Turns reichen meistens — breiter zu gehen hilft selten und verteuert den Prompt; schmaler zu gehen lässt den Agent vergessen, was der Kunde vor einer Minute gesagt hat.

3

Adaptiver Kontext

Hol den Kontext nur, wenn die eingehende Nachricht wie ein Follow-up aussieht. Eine billige Heuristik auf dem Text — beginnt mit einem Pronomen, bezieht sich auf „das“ oder „es“, kommt innerhalb von 30 Sekunden nach deiner vorherigen Antwort, kurze Einwort-Acknowledgments — entscheidet, ob du Verlauf holst oder zustandslos antwortest. Die meisten Turns bleiben billig; Follow-ups bekommen Gedächtnis. Fang mit dem vollen Fenster an und wechsle nur dann auf adaptiv, wenn du dein Kostenbudget kennst und echten Produktionstraffic hast, um die Heuristik zu tunen.

Das Fenster zu holen ist ein einzelnes GET. Die Antwort ist ein flaches Array von Nachrichten des Chats, die neueste zuletzt, gemischt aus eingehenden Kundennachrichten, deinen eigenen ausgehenden Antworten und allen Notizen, die dein Skill geschrieben hat:

$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    "https://app.timelines.ai/integrations/api/chats/12345678/messages?limit=20"
{"status":"ok","data":[
  {"message_uid":"INBOUND-UID-PLACEHOLDER",
   "text":"hi, is the blue one still available?",
   "sender_phone":"+15550200",
   "message_type":"text","origin":"WhatsApp",
   "created_at":"2026-04-14T10:22:03Z"},
  {"message_uid":"OUTBOUND-UID-PLACEHOLDER",
   "text":"Yes, one left. Want me to hold it for you?",
   "sender_phone":"",
   "message_type":"text","origin":"Public API",
   "created_at":"2026-04-14T10:22:41Z"},
  {"message_uid":"INBOUND-UID-PLACEHOLDER-2",
   "text":"yes please, until tomorrow",
   "sender_phone":"+15550200",
   "message_type":"text","origin":"WhatsApp",
   "created_at":"2026-04-14T10:23:08Z"}
]}

Fallen, die in der Produktion beißen

!
Filtere Notizen und deine eigenen ausgehenden Nachrichten aus dem Verlauf. GET /chats/{id}/messages gibt alles zurück, was im Chat ist: eingehende Kundennachrichten, deine eigenen ausgehenden Antworten und alle Notizen, die dein Skill für State oder Debug geschrieben hat. Notizen tragen message_type == "note" und origin == "Public API" — filtere sie, bevor du den Verlauf ans Modell übergibst, sonst sieht dein Agent sein eigenes internes Bookkeeping als echtes Gespräch und fängt an, auf seine eigenen Notizen zu antworten.
!
Mitten im Gespräch einsteigen. Wenn dein Skill einen Chat zum ersten Mal sieht, hat dieser Chat möglicherweise bereits einen langen Verlauf von vor dem Webhook-Subscribe. Hol immer aktuellen Kontext bei einem dir unbekannten Chat — sonst antwortest du auf ein brandneues „hi“, als wäre es eine frische Eröffnung, während der Kunde tatsächlich dreißig Turns tief in einem bestehenden Thread mit deinem Team steckt.
!
Dedupliziere Webhook-Bursts. Wenn ein Kunde drei Nachrichten in vier Sekunden tippt, bekommst du drei Webhooks — möglicherweise parallel. Ohne einen Per-Chat-Lock oder ein kurzes Debounce bekommt der Kunde drei sich überlappende Antworten. Muster: halte das erste Webhook 1–2 Sekunden fest, hol dann einmal den Verlauf und antworte auf den kombinierten Zustand. Webhook-Retries (bis zu 3 Versuche, je 5 Sekunden Timeout) kommen auf demselben Weg — dedupliziere auf message_uid, bevor du verarbeitest.
!
Sortiere nach created_at, nicht nach message_uid. message_uid ist workspace-scoped und nicht global sortierbar. Wenn du den Verlauf im Prompt zusammenstellst, sortiere die Turns nach dem created_at-Timestamp aus dem Payload, nicht nach UID. Cross-Workspace-UIDs werden auch nicht geteilt — dieselbe physische WhatsApp-Nachricht, die an zwei unterschiedliche TimelinesAI-Workspaces geliefert wird, hat in jedem eine andere UID.

State-Persistenz

OpenClaw-Skills halten zwischen Aufrufen keinen State im Speicher. WhatsApp-Gespräche sind multi-turn. Die Lösung ist, den State am Chat selbst zu speichern:

  • Labels halten die diskrete Stage — discovery/q1, qualified, escalate. Hinzufügen mit POST /chats/{id}/labels, lesen mit GET /chats/{id}/labels.
  • Notizen halten strukturierte Daten — team_size=8, Antwort-Entwürfe, Lead-Scores. Hinzufügen mit POST /chats/{id}/notes. Lesen durch Iterieren über GET /chats/{id}/messages und Filter auf message_type == "note".

Vorteil: Crash-Sicherheit, Sichtbarkeit für menschliche Teamkollegen, sauberer Handoff — ein Mensch kann ein Label löschen, um den Flow zurückzuspulen, oder escalate hinzufügen, um zu übernehmen. Der Trade-off: jede State-Transition ist ein HTTP-Call. Für kundenbezogene Flows ist das in Ordnung.


Aus der richtigen Nummer senden

Wenn dein Workspace mehr als eine WhatsApp-Nummer verbunden hat, muss dein Skill sicherstellen, dass er aus der gemeinten Nummer sendet. Jeder Chat hat ein Feld whatsapp_account_id mit der vollen JID (wie TELEFON@s.whatsapp.net) der besitzenden Nummer. Wenn du POST /chats/{id}/messages aufrufst, ist der Sender immer diese JID — du wählst sie nicht, der Chat tut es.

Das Muster:

  1. 1Hardcode die erlaubte Sender-JID im Environment jedes Skills (z. B. ALLOWED_SENDER_JID).
  2. 2Vor dem Senden GET /chats/{id} und vergleich whatsapp_account_id mit deiner erlaubten JID.
  3. 3Wenn sie nicht passen, überspring den Send — an einen Menschen eskalieren oder das Event verwerfen.

Zwei zusätzliche HTTP-Calls pro Turn, null Chance, aus dem falschen Persona zu senden. Für Single-Number-Workspaces gilt das nicht.


Eingehend

Eingehende Nachrichten — was dein Agent verarbeiten kann

Jedes Mal, wenn ein Kunde an deine WhatsApp-Nummer schreibt, feuert TimelinesAI ein message:received:new-Event an deinen Webhook mit Chat-ID, Text, Sender-Telefon und Anhängen. Dein Skill liest das Event, entscheidet, was zu tun ist, und antwortet mit POST /chats/{chat_id}/messages. Alles unten ist eine Variation dieser Schleife.

1

Auf jede eingehende Nachricht automatisch antworten

Beantworte Fragen zu Versand, Rücksendungen und Öffnungszeiten. Für alles andere entwirf eine Antwort und markiere den Chat zur Überprüfung.
Zwischen 22:00 und 08:00 antworte automatisch. Während der Geschäftszeiten flagge mir nur die eingehenden Chats.
Übernimm meine WhatsApp-Antworten, während ich in diesem Meeting bin.
How it works

der Skill empfängt das Webhook-Payload, formuliert eine Antwort und ruft POST /chats/{id}/messages mit {"text":"..."} auf. Standardmäßig zustandslos — ein API-Call pro Turn. Für Mehrfach-Turn-Gespräche, in denen der Agent sich an frühere Nachrichten erinnern muss, siehe Gesprächskontext unten.

2

FAQ-Handler mit Eskalation an einen Menschen

Beantworte Fragen zu Versand, Rücksendungen und Öffnungszeiten. Für alles andere markiere den Chat needs-human und höre auf zu antworten, bis ich das Label lösche.
How it works

vor dem Antworten GET /chats/{id}/labels prüfen. Hat der Chat needs-human, ohne Sendung verlassen. Passt der eingehende Text zu einem FAQ-Thema, antworten. Sonst POST /chats/{id}/labels mit dem Eskalations-Tag und stillschweigend verlassen. Der Posteingang deines Teams filtert nach dem Label.

3

Gespräche an die richtige Person routen

Bestimme für jeden eingehenden Chat, ob es um Sales, Support oder Billing geht, und tagge ihn. Weise Sales-Chats an alex@ours zu und Billing an jamie@ours.
How it works

Intent aus dem Text klassifizieren, POST /chats/{id}/labels mit intent/sales o. ä., dann PATCH /chats/{id} mit {"responsible_email":"..."}, um den Chat im TimelinesAI-Posteingang zu übergeben.

4

Leads durch eine Fragesequenz qualifizieren

Für jeden neuen Chat aus unserer Facebook-Kampagne nach Use Case, Teamgröße und Timeline fragen. Antworten als Notizen am Chat speichern. Tagge qualified, wenn die Teamgröße 5 oder mehr ist.
How it works

Labels markieren, bei welcher Frage du gerade bist (discovery/q1, q2, q3); Notizen speichern die Antworten. Pro Turn: aktuelles Stage-Label lesen, eingehenden Text als Antwort parsen, mit POST /chats/{id}/notes ablegen, Label fortschreiben, nächste Frage stellen. Keine externe Datenbank — siehe State-Persistenz unten.

$ # 1. Read current stage from the chat's labels. Labels nest under
$ # data.labels as a string array — see Response shapes in Things to know.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    https://app.timelines.ai/integrations/api/chats/12345678/labels
{"status":"ok","data":{"labels":["discovery/q1"]}}

$ # 2. Save the customer's answer as a structured note. Notes are stored
$ # as messages with message_type=note and aren't pushed to WhatsApp.
$ cat > /tmp/note.json <<'JSON'
{"text":"team_size=12"}
JSON
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/note.json \
    https://app.timelines.ai/integrations/api/chats/12345678/notes
{"status":"ok","data":{"message_uid":"NOTE-UID-PLACEHOLDER"}}

$ # 3. Advance the stage label. POST /labels REPLACES the full set, so
$ # read existing labels, swap q1 for q2, POST the combined list. The
$ # body key is "labels" (plural, array) — not "label" singular.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    https://app.timelines.ai/integrations/api/chats/12345678/labels \
  | jq -c '.data.labels | map(if . == "discovery/q1" then "discovery/q2" else . end) | {labels: .}' \
  > /tmp/labels.json
$ cat /tmp/labels.json
{"labels":["discovery/q2"]}
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/labels.json \
    https://app.timelines.ai/integrations/api/chats/12345678/labels
{"status":"ok","data":{"labels":["discovery/q2"]}}
5

Fotos, PDFs und Belege verstehen

Wenn ein Kunde ein Foto eines Belegs schickt, extrahiere Betrag und Händler und füg sie als Notiz hinzu.
Wenn jemand ein PDF schickt, klassifiziere es als Rechnung / Vertrag / Ausweis und tagge den Chat entsprechend.
How it works

Webhook-Payloads enthalten eine Anhang-URL. Lade sie inline im Handler herunter (sie läuft schnell ab), verarbeite mit den Vision- oder Dokument-Tools von OpenClaw und schreib die extrahierten Daten via POST /chats/{id}/notes zurück.

$ # The attachment URL in the webhook payload expires in ~15 minutes.
$ # Download it inline in the receiver, before queuing async work.
$ ATTACH_URL="https://files.timelines.ai/abc123/receipt.jpg?expires=..."
$ curl -sS "$ATTACH_URL" -o /tmp/receipt.jpg

$ # Process /tmp/receipt.jpg with OpenClaw vision tools, then write
$ # the extracted fields back as a structured note on the chat:
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary '{"text":"receipt: amount=$42.50 vendor=Cafe Madrid"}' \
    https://app.timelines.ai/integrations/api/chats/12345678/notes
6

Sprachnachrichten transkribieren und antworten

Transkribiere eingehende Sprachnachrichten. Antworte als Text — wenn du wirklich eine Sprachantwort brauchst, schick sie aus dem TimelinesAI-Dashboard.
How it works

der Webhook liefert eine URL zur Sprachdatei. Herunterladen, transkribieren, Textantwort via POST /chats/{id}/messages formulieren. Sprachantworten sind heute ein Feature des TimelinesAI-Dashboards und stehen nicht in der aktuellen Public-API-Referenz — wenn dein Skill Sprachantworten programmatisch senden muss, kontaktier den TimelinesAI-Support, um zu prüfen, ob der Legacy-voice_message-Endpoint in deinem Workspace noch verfügbar ist.

7

Die Sprache des Kunden treffen

Wenn der Kunde auf Spanisch schreibt, antworte auf Spanisch. Wenn er mitten im Gespräch die Sprache wechselt, wechsle mit.
How it works

reines OpenClaw-seitiges Reasoning über den eingehenden Text. TimelinesAI trägt nur die Antwort.

8

Auf Nachrichten reagieren, ohne eine vollständige Antwort zu senden

Reagiere mit 👀 auf jede eingehende Nachricht, damit Kunden wissen, dass ich sie gesehen habe, dann formuliere ich die echte Antwort in Ruhe.
How it works

PATCH /messages/{uid}/reactions mit {"reaction":"👀"}. Das reaction-Feld muss das reale Emoji-Zeichen tragen — Shortcodes wie "eyes" oder ":eyes:" werden mit HTTP 400 "Reaction has invalid format" abgelehnt. Kein Nachrichten-Credit verbraucht — Reaktionen sind leichtgewichtig.

$ # Send the raw emoji character in the body. Save to a UTF-8 file and
$ # use --data-binary so shells can't downgrade it.
$ printf '{"reaction":"👀"}' > /tmp/react.json

$ curl -sS -X PATCH \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/react.json \
    https://app.timelines.ai/integrations/api/messages/INBOUND-UID-PLACEHOLDER/reactions
{"status":"ok"}

Ausgehend

Ausgehend — Nachrichten, die dein Agent startet

Nachrichten, die dein Agent initiiert, keine Antworten. Ausgelöst durch ein Event in deinen anderen Tools (eine neue Bestellung, eine fehlgeschlagene Zahlung, ein gebuchtes Meeting) oder eine direkte menschliche Anweisung zu einer bestimmten Person.

Vor jeder Fähigkeit in diesem Abschnitt: der Kunde hat den Thread entweder vor Kurzem geöffnet (innerhalb des 24-Stunden-Session-Fensters von WhatsApp) oder erwartet diese Nachricht explizit. Wenn keines davon stimmt, sende es nicht von einer Personal Number — das ist Business-API-Land.

9

Eine transaktionale Nachricht per Name oder an einen neuen Empfänger senden

Sag John, dass seine Rechnung bereit ist.
Schick dem Klempner unsere neue Büroadresse, damit er die Teile liefern kann.
Schick den unterschriebenen Vertrag an den Kunden, der gerade die Anzahlung überwiesen hat.
How it works

für einen bestehenden Chat per GET /chats?name=John (oder dein CRM) suchen und POST /chats/{id}/messages aufrufen. Für einen neuen Empfänger POST /messages mit {"phone":"+...","text":"..."}. Der whatsapp-send-Skill im Companion-Repo handhabt beide Modi mit UTF-8-sicherer Serialisierung.

$ cat > /tmp/send.json <<'JSON'
{"phone":"+15550200",
 "text":"Hi - your order shipped. Tracking: ABC123."}
JSON

$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/send.json \
    https://app.timelines.ai/integrations/api/messages
{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}
10

Geplante Follow-ups innerhalb eines aktiven Gesprächs

Jeden Montag um 9 Uhr Chats mit Tag to-follow-up prüfen, die in den letzten 24 Stunden Kundenaktivität hatten, aber keine Antwort von uns, und einen sanften Reminder schicken.
How it works

plane es mit einem OpenClaw-Cronjob oder einer Standing Order (siehe OpenClaws Automation-Docs). Der Job holt das Publikum mit GET /chats?label=to-follow-up&read=false, liest last_message_timestamp jedes Chats und sendet nur, wenn der letzte Kunden-Turn weniger als 24 Stunden zurückliegt. Außerhalb dieses Fensters wird ein Follow-up zu Re-Engagement und du bist wieder im Business-API-Land — siehe Channel choice.

11

Nachrichten aus Events in deinen anderen Tools auslösen

Wenn ein HubSpot-Deal auf 'demo scheduled' geht, sende eine WhatsApp-Bestätigung mit dem Meeting-Link.
Wenn Stripe eine fehlgeschlagene Zahlung meldet, sende eine höfliche Recovery-Nachricht mit einem Link zum Karten-Update.
Wenn eine Calendly-Buchung erstellt wird, sende am Morgen einen Pre-Meeting-Reminder.
How it works

dein bestehendes Tool (HubSpot, Stripe, Calendly, Pipedrive) feuert seinen eigenen Webhook auf denselben Receiver, den du im Setup eingerichtet hast — füg einen Handler hinzu, der auf Event-Source oder Request-Pfad verzweigt, branche vom TimelinesAI-Handler ab und ruf POST /messages (neuer Empfänger) oder POST /chats/{id}/messages (in einen bestehenden Chat) auf. WhatsApp wird zum Auslieferungskanal für jeden Workflow, den du schon hast. Da die Sendungen einer Kundenaktion im Upstream-Tool folgen, sind sie von Natur aus transaktional — voll im Rahmen der Personal-Number-Regeln.

12

Dateien und Dokumente auf Anfrage senden

Generiere das Angebot-PDF und schick es dem Kunden, der gerade nach Preisen gefragt hat.
Schick den Vertrag als PDF per E-Mail und leg ihn auch in den WhatsApp-Chat des Kunden.
How it works

zwei Schritte. Datei-Bytes mit POST /files_upload als multipart/form-data hochladen (es gibt keinen Upload-per-URL-Endpoint — dein Agent muss die Datei zuerst herunterladen und dann POSTen). Die Antwort liefert eine uid, die du an POST /chats/{id}/messages im Feld file_uid weitergibst. Der Kunde hat das Dokument angefragt — das ist eine Antwort auf seine Bitte, kein Cold Outreach.

$ # Step 1 - upload the file bytes as multipart/form-data.
$ # The form field name is "file". The response gives you a uid.
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -F "file=@/path/to/quote.pdf" \
    https://app.timelines.ai/integrations/api/files_upload
{"status":"ok","data":{
  "uid":"FILE-UID-PLACEHOLDER",
  "filename":"quote.pdf",
  "size":128453,
  "mimetype":"application/pdf",
  "uploaded_by_email":"you@yourcompany.com",
  "uploaded_at":"2026-04-14 10:22:03 +0000",
  "temporary_download_url":"https://tl-prod-data.s3.amazonaws.com/..."
}}

$ # Step 2 - attach the uploaded file to a chat message using file_uid.
$ cat > /tmp/send.json <<'JSON'
{"text":"Quote attached for your review.",
 "file_uid":"FILE-UID-PLACEHOLDER"}
JSON
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/send.json \
    https://app.timelines.ai/integrations/api/chats/12345678/messages
{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}
13

Prüfen, ob eine Nachricht tatsächlich zugestellt wurde

Hat John die Rechnung wirklich bekommen, die ich heute Morgen geschickt habe?
How it works

jeder Send liefert eine message_uid. Später gibt GET /messages/{uid}/status_history die Sent / Delivered / Read-Timeline zurück. Die Zustellung erfolgt normalerweise innerhalb von ein bis zwei Sekunden auf einer aktiven Nummer. Der whatsapp-delivery-check-Skill im Companion-Repo kapselt das.

$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    https://app.timelines.ai/integrations/api/messages/OUTBOUND-UID-PLACEHOLDER/status_history
{"status":"ok","data":[
  {"status":"Sent",     "timestamp":"2026-04-12 12:28:40 +0000"},
  {"status":"Delivered","timestamp":"2026-04-12 12:28:41 +0000"}
]}

CRM

CRM und Analyse

Lese-Endpoints geben deinem Agent genug Daten, um analytische Fragen zu beantworten und State mit deinem CRM in natürlicher Sprache zu synchronisieren.

14

Reaktionszeit-Reporting

Wie war unsere durchschnittliche Erstantwortzeit auf WhatsApp diese Woche?
Wer in meinem Team ist beim Antworten am langsamsten?
How it works

jüngste Chats holen, dann für jeden Chat die Nachrichtenzeitleiste holen, die erste ausgehende Nachricht nach jeder eingehenden finden und die Deltas client-seitig aggregieren. Zwei Endpoints, kein spezieller Analytics-Call.

$ # Pull the last page of a chat's messages (50 per page, fixed).
$ # Messages nest under data.messages — not a flat .data[] array.
$ # FILTER message_type=="whatsapp" to exclude notes (see Things to know
$ # below — notes live in the same timeline with from_me=false and would
$ # otherwise corrupt the response-time calculation).
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    "https://app.timelines.ai/integrations/api/chats/12345678/messages?page=1" \
  | jq -r '.data.messages[] | select(.message_type=="whatsapp") | "\(.timestamp)\tfrom_me=\(.from_me)\t\(.text[0:40])"'

# 2026-04-12 12:28:40 +0300  from_me=false  Hi - is the order shipped?
# 2026-04-12 12:30:15 +0300  from_me=true   Yes - tracking ABC123, ETA 2-3
# 2026-04-12 14:02:11 +0300  from_me=false  Got it, thanks!
# ...

$ # Compute first-reply latency from each consecutive (false -> true)
$ # pair, then average across all chats. Pure client-side aggregation.
$ # For full history walk ?page=2, ?page=3, ... until data.has_more_pages is false.
15

Erkennung unbeantworteter Nachrichten

Wie viele Nachrichten haben wir gestern erhalten? Wie viele sind noch unbeantwortet?
Zeig mir jeden Chat mit einer eingehenden Nachricht in den letzten 24 Stunden ohne Antwort.
How it works

GET /chats?read=false für ungelesene Chats, dann auf .data.messages[] filtern wo message_type=="whatsapp" UND from_me==false — Notes tragen auch from_me=false und würden den Unbeantwortet-Count aufblähen. Siehe Wichtig zu wissen.

16

Gespräche on demand zusammenfassen

Fasse das gesamte Gespräch mit ACME Corp zusammen. Was sind ihre Pain Points?
Gib mir ein einseitiges Briefing zu jedem Chat, den ich noch nicht beantwortet habe.
How it works

GET /chats/{id}/messages holen, auf .data.messages[] mit message_type=="whatsapp" filtern (Notes würden sonst die früheren Kritzeleien deines eigenen Agents wie Kundenaussagen in die Zusammenfassung schleusen — siehe Wichtig zu wissen), dann OpenClaw zusammenfassen lassen. Mit ?page=N paginieren für lange Threads.

17

Dein CRM mit WhatsApp-Aktivität anreichern

Für jeden neuen Chat dieser Woche die Nummer in HubSpot nachschlagen. Wenn es ein Kontakt ist, den Chat mit seinem Deal-Stage taggen. Wenn nicht, den Kontakt anlegen.
How it works

TimelinesAI-Reads mit der API deines CRMs kombinieren. Ergebnisse via POST /chats/{id}/labels und POST /chats/{id}/notes zurückschreiben.

$ # 1. Pull recent chats with their phone numbers. /chats returns 50 per
$ # page under data.chats — paginate with ?page=N if you need more.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    "https://app.timelines.ai/integrations/api/chats?page=1" \
  | jq -r '.data.chats[] | "\(.id)\t\(.phone)"'

# 12345678   +15550100
# 12345679   +15550200
# ...

$ # 2. For each phone, look up your CRM (HubSpot/Pipedrive/Close).
$ #    Pseudo-code:
$ #      contact = hubspot.search_by_phone(phone)
$ #      stage   = contact.deal_stage if contact else "unknown"

$ # 3. Add the deal stage label WITHOUT wiping existing labels.
$ # POST /labels is REPLACE semantics (full set), so GET current labels,
$ # append the new one, and POST the combined list.
$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    https://app.timelines.ai/integrations/api/chats/12345678/labels \
  | jq -c '.data.labels + ["hubspot/qualified-lead"] | unique | {labels: .}' \
  > /tmp/labels.json
$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/labels.json \
    https://app.timelines.ai/integrations/api/chats/12345678/labels
18

Leads aus dem Gesprächsinhalt scoren

Score jeden Chat mit Tag inbound-lead von 1 bis 10 nach Fit und Dringlichkeit. Schreib den Score als Notiz.
How it works

LLM-Reasoning über GET /chats/{id}/messages — vorher auf message_type=="whatsapp" filtern, sonst werden deine eigenen früheren Lead-Score-Notes als Kundenaussagen re-gelesen und der Score driftet bei jedem Durchlauf. Ergebnis via POST /chats/{id}/notes mit berechenbarem Prefix schreiben (z.B. "lead_score: fit=8 urgency=6"), damit dein nächster Lauf es finden und überschreiben kann.


Betrieb

Skalieren und Handoff

Muster für Human-in-the-Loop-Workflows, Multi-Agent-Routing und Conversation-Memory. Alle vier Fähigkeiten unten sind so entworfen, dass sie mit Menschen koexistieren, die dieselben Chats aus dem geteilten Posteingang von TimelinesAI bearbeiten.

19

Antworten für menschliche Überprüfung entwerfen statt zu senden

Für jede neue eingehende Nachricht eine Antwort entwerfen und als Notiz speichern. Nicht senden — ich prüfe und sende selbst.
How it works

POST /chats/{id}/notes mit dem Entwurfstext statt /messages. Die Notiz erscheint in derselben Chat-Ansicht, die dein Team bereits nutzt.

20

An einen Menschen übergeben, wenn der Agent feststeckt

Wenn das Gespräch mehr als 5 Turns ohne Auflösung dauert, oder der Kunde nach einem Menschen fragt, tagge escalate und höre auf zu antworten, bis ich das Label entferne.
How it works

Turns mit GET /chats/{id}/messages zählen, Stop-Reply-Labels mit GET /chats/{id}/labels vor jedem Send prüfen. Löst die Eskalation aus, POST /chats/{id}/labels mit escalate und beenden.

$ # 1. Count outbound 'whatsapp' messages (skip notes) in this chat.
$ # Messages nest under data.messages — not a flat .data[] array.
$ TURNS=$(curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    "https://app.timelines.ai/integrations/api/chats/12345678/messages?page=1" \
  | jq '[.data.messages[] | select(.from_me==true and .message_type=="whatsapp")] | length')
$ echo $TURNS
8

$ # 2. Past the threshold? Append 'escalate' to the chat's labels and stop.
$ # POST /labels REPLACES the full set, so read existing first, combine,
$ # then POST — otherwise you wipe every other label on the chat.
$ if [ "$TURNS" -ge 5 ]; then
    curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
      https://app.timelines.ai/integrations/api/chats/12345678/labels \
    | jq -c '.data.labels + ["escalate"] | unique | {labels: .}' \
    > /tmp/labels.json
    curl -sS -X POST \
      -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
      -H "Content-Type: application/json" \
      --data-binary @/tmp/labels.json \
      https://app.timelines.ai/integrations/api/chats/12345678/labels
  fi

$ # 3. Before every future send, GET /chats/{id}/labels and bail
$ #    if .data.labels contains 'escalate' or 'needs-human'.
21

Mehrere spezialisierte Agents auf einem Posteingang laufen lassen

Sales-AI übernimmt Preisfragen, Support-AI übernimmt Produktfragen. Nach Intent routen; wenn beide unsicher sind, an mich eskalieren.
How it works

zwei Spezialisten-Skills in einem OpenClaw-Workspace (einer für Sales, einer für Support) plus ein dritter Intent-Klassifikations-Skill, der zuerst auf jede eingehende Nachricht läuft, den Chat mit dem gewählten Spezialisten taggt und rausgeht. Jeder Spezialist liest das Intent-Label vor dem Antworten und bricht ab, wenn es auf den anderen zeigt — so feuert exakt ein Skill pro Nachricht.

22

Sich an frühere Gespräche mit demselben Kunden erinnern

Letzte Woche hast du erwähnt, dass du verreist warst — wie war’s?
How it works

OpenClaws eigenes Memory plus GET /chats/{id}/messages für die volle WhatsApp-Historie. Der Verlauf überlebt zwischen Aufrufen, weil er auf TimelinesAI lebt.


Testing

Teste deinen Agent end-to-end mit einer zweiten WhatsApp-Nummer

Nach dem Setup kannst du die Capabilities hier gelistet sehen, aber du kannst deinen Agent nicht wirklich arbeiten sehen, bis ein echter Kunde ihn anschreibt. Das ist ein schlechter Loop zum Iterieren. Hier ein besserer: verbinde eine zweite WhatsApp-Nummer mit demselben TimelinesAI-Workspace, benutze sie als gescripteten Kunden und beobachte, wie dein echter Agent-Skill die Konversation end-to-end führt. Keine Mocks, keine fake Webhooks — es ist dein echter Agent, deine echte API, nur mit einer zweiten Nummer, die die Rolle des Menschen auf der anderen Seite spielt.

Voraussetzung — zwei verbundene WhatsApp-Nummern. Dieses Muster braucht zwei Nummern in deinem TimelinesAI-Workspace: eine als Kunden-Persona, eine die deinen echten Agent-Stack läuft. Wenn du gerade nur eine verbundene Nummer hast, spring stattdessen zu Capability #19 (Drafts zur menschlichen Review) — sie gibt dir einen langsameren, aber immer noch nützlichen Iterations-Loop, wo dein Agent Antworten als Notes draftet und du sie im Dashboard freigibst.

Das Muster

TimelinesAI lässt mehrere WhatsApp-Nummern in einem Workspace leben und routet ihre eingehenden Webhooks durch denselben Receiver. Der Trick ist das whatsapp_account_id-Feld auf jedem Webhook-Payload — es trägt den JID der Nummer, die die Nachricht empfangen hat. Lies es, schalte darauf, und dispatche entweder zum Persona-Schritt (wenn dein Agent dem Kunden gerade geantwortet hat) oder zum Agent-Schritt (wenn der Kunde deinem Agent gerade geschrieben hat). Jede Seite sendet über das normale POST /messages oder POST /chats/{id}/messages aus Sicht der anderen Seite.

Architektur

  [+1 555 0100]                   [+1 555 0200]
     persona                    agent under test
        |                              |
        |    one TimelinesAI workspace |
        |                              |
        +------->  Public API  <-------+
                        |
                 message:received:new
                        |
                        v
           +--------------------------+
           |  Your test receiver      |
           |  switch (accountJid) {   |
           |    persona_jid -> next   |
           |    agent_jid   -> skill  |
           |  }                       |
           +--------------------------+

Persona-Sender

Die Persona-Seite ist ein gescripteter Kunde: eine Liste von Zeilen, die sie als Nächstes sagt. Wenn es Zeit für den nächsten Turn ist, POST an /messages mit der Telefonnummer des Agents als Empfänger. TimelinesAI macht das Routing, weil beide Nummern in einem Workspace sind.

// persona-sender.js — send the next scripted customer turn from the
// persona WhatsApp number to the agent-under-test number. TimelinesAI
// routes the send because both numbers share one workspace.

const PERSONA_SCRIPT = [
  "hi I saw your ad",
  "we want to reduce customer churn",
  "we are 25 people",
  "we need to roll out in Q3",
];
let idx = 0;

export async function sendNextPersonaTurn() {
  if (idx >= PERSONA_SCRIPT.length) return;
  const text = PERSONA_SCRIPT[idx++];

  await fetch("https://app.timelines.ai/integrations/api/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.TIMELINES_AI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: Buffer.from(JSON.stringify({
      phone: process.env.AGENT_UNDER_TEST_PHONE,
      text,
    }), "utf-8"),
  });
  console.log(`[persona] sent: ${text}`);
}

Receiver mit JID-Switch

Ein Receiver, ein Branch pro Seite. Alles andere, was du schon aus dem Setup kennst — die ?secret=-Auth, der from_me-Echo-Filter, der flache-vs-verschachtelte Payload-Fallback — gilt hier unverändert.

// api/webhook.js — route events by which number received them
export default async function handler(req, res) {
  if (req.query.secret !== process.env.WEBHOOK_SECRET) return res.status(404).end();
  const { event_type, data } = req.body || {};
  if (event_type !== "message:received:new") return res.status(200).end();
  if (data?.from_me === true) return res.status(200).end();
  res.status(200).json({ ok: true });

  const accountJid = data.whatsapp_account_id ?? data.chat?.whatsapp_account_id;
  if (accountJid === process.env.AGENT_UNDER_TEST_JID) {
    // Customer just messaged the agent — run your agent-under-test skill
    await handleAgentTurn(data);
  } else if (accountJid === process.env.PERSONA_JID) {
    // Agent just replied to the persona — advance the scripted scenario
    await advancePersona(data);
  }
}

Diese zwei Snippets sind die Kern-Idee. Der volle Harness — Persona-Skripte, Scenario-Loader, CLI-Kickoff, farbiger Split-Pane-Logger, Vercel-Config — lebt in examples/test-harness/ im Companion-Repo, mit einer eigenen README, die durch Deploy, Env Vars, Webhook-Registrierung und das erste Scenario end-to-end führt.

Zusehen, wie es passiert

Der Harness läuft autonom — du musst nicht jede Antwort freigeben. Deine Aufsicht passiert im TimelinesAI-Dashboard: öffne den Chat zwischen deiner Persona- und Agent-Nummer in einem anderen Browser-Tab, und jeder Turn erscheint live. Wenn der Agent etwas Dummes sagt, schick eine manuelle Antwort aus der Agent-Inbox, um die Konversation zu lenken. Wenn du Verhalten mitten im Run ändern musst, setz ein Label oder schreib eine Note auf den Chat — beides ist für den Agent-Skill beim nächsten Turn sichtbar. Stopp den Run, indem du den registrierten Webhook löschst oder die Vercel-Funktion abschaltest; das Scenario hält sofort an.


Beispiel

Beispiel: ein vollständiger Round Trip

Eine konkrete Fünf-Schritt-Schleife, die zeigt, wie die API in der Praxis aussieht — wie dein Agent einen Outbound sendet, die Zustellung bestätigt, die Antwort des Kunden verarbeitet und ein Follow-up sendet. Telefonnummern, Chat-IDs und message UIDs unten sind Platzhalter — ersetz sie durch deine eigenen.

Im gesamten Beispiel verwendete Platzhalter:

Your business number   → +1 555 0100 (JID: 15550100@s.whatsapp.net)
Your customer's number → +1 555 0200
API token              → $TIMELINES_AI_API_KEY
1

Prüfe, dass dein Token funktioniert, und liste deine verbundenen Nummern auf

$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    https://app.timelines.ai/integrations/api/whatsapp_accounts

{"status":"ok","data":{"whatsapp_accounts":[
  {"id":"15550100@s.whatsapp.net","phone":"+15550100",
   "status":"active","account_name":"Your Business"}
]}}

Du solltest eine Liste deiner verbundenen Nummern sehen. Wenn der Status nicht active ist, behebe das im TimelinesAI-Dashboard, bevor du weitermachst.

2

Sende eine ausgehende Nachricht

Schreib das Payload mit expliziter UTF-8-Codierung in eine Datei und gib es dann curl. Das ist das Muster für jeden Send.

$ cat > /tmp/send.json <<'JSON'
{"phone":"+15550200",
 "text":"Hi - your order shipped. Tracking: ABC123."}
JSON

$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/send.json \
    https://app.timelines.ai/integrations/api/messages

{"status":"ok","data":{"message_uid":"OUTBOUND-UID-PLACEHOLDER"}}

Die Antwort ist eine Quittung, keine Zustellbestätigung. Heb die message_uid für den nächsten Schritt auf.

3

Prüfe den Zustellstatus

$ curl -sS -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    .../messages/OUTBOUND-UID-PLACEHOLDER/status_history

{"status":"ok","data":[
  {"status":"Sent",     "timestamp":"2026-04-12 12:28:40 +0000"},
  {"status":"Delivered","timestamp":"2026-04-12 12:28:41 +0000"}
]}

Sent → Delivered passiert auf einer aktiven Nummer typischerweise innerhalb einer Sekunde. Der Read-Status erscheint später, wenn der Empfänger den Chat tatsächlich öffnet.

4

Der Kunde antwortet, dein Webhook feuert

Wenn der Kunde antwortet, postet TimelinesAI an die Webhook-URL, die du registriert hast:

{
  "event_type": "message:received:new",
  "data": {
    "chat_id": 12345678,
    "message_uid": "INBOUND-UID-PLACEHOLDER",
    "sender_phone": "+15550200",
    "sender_name": "Customer",
    "text": "Thanks! When will it arrive?",
    "timestamp": "2026-04-12 12:29:20 +0000"
  }
}

Dein Receiver acked sofort mit 200 und reicht das Payload dann an deinen OpenClaw-Skill weiter.

5

Antworte aus demselben Chat zurück

$ cat > /tmp/reply.json <<'JSON'
{"text":"Estimated delivery is 2-3 business days. You'll get tracking updates to this chat."}
JSON

$ curl -sS -X POST \
    -H "Authorization: Bearer $TIMELINES_AI_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @/tmp/reply.json \
    https://app.timelines.ai/integrations/api/chats/12345678/messages

{"status":"ok","data":{"message_uid":"REPLY-UID-PLACEHOLDER"}}

Da du in einen bestehenden Chat über /chats/{id}/messages sendest, ist der Sender automatisch die WhatsApp-Nummer, der der Chat gehört — du wählst sie nicht, der Chat-Datensatz tut es. Genau deshalb sendest du in Multi-Nummer-Workspaces nicht versehentlich von der falschen Nummer.


Grenzen

Grenzen und Vorbehalte

Was dieser Guide NICHT abdeckt und warum.

!
Personal Numbers werden bei Cold Outreach gesperrt. Unaufgeforderte Broadcasts von Personal Numbers sind kein unterstützter Use Case. Die Sperre wird auf der Infrastrukturebene von WhatsApp durchgesetzt, nicht im TimelinesAI-Gateway, deshalb können wir dich davor nicht schützen.
!
Das 24-Stunden-Customer-Service-Fenster von WhatsApp. Du kannst 24 Stunden nach der letzten Nachricht eines Kunden frei antworten. Außerhalb dieses Fensters brauchen Nachrichten Opt-in und Templates — Business-API-Land.
!
Asynchrone Zustellung. POST /messages liefert eine message_uid (eine Quittung), keine Zustellbestätigung. Nutze /status_history, um die echte Zustellung zu bestätigen.
!
Anhang-URLs laufen schnell ab. Lade Medien inline im Webhook-Handler herunter, nicht aus einem verzögerten Worker.
!
Nur Text, Medien, Reaktionen und Metadaten — keine Sprach-/Videoanrufe, kein Broadcast-Status, keine WhatsApp Channels oder Stories.

Bausteine

Companion-Skill-Bundle auf GitHub →

4 funktionierende Skills, ein Vercel-Webhook-Receiver, Compliance-Docs und ein vollständiger Mirror dieses Guides. MIT-lizenziert.

TimelinesAI API-Docs · OpenClaw Skills Docs · v0.1.0-Release

Fähigkeits-Guide · 2026 · Kanonische URL timelines.ai/guide/openclaw-whatsapp-skills