April 20, 2026

WhatsApp-Ausgangs-Sendefehler mit Allowlist-Richtlinie - WhatsApp Outbound Send Failure with Allowlist Policy

Wenn die WhatsApp dmPolicy 'allowlist' verwendet wird, schlagen ausgehende Nachrichten mit 'Delivering to WhatsApp requires target' fehl, weil sendTo-Ziele in derselben allowFrom-Liste sein müssen, die den eingehenden Zugriff steuert.

🔍 Symptome

Primäre Fehlererscheinung

Beim Versuch, eine WhatsApp-Nachricht mit dem message-Tool an einen Kontakt zu senden, der nicht in der allowFrom-Liste enthalten ist:

Error: Delivering to WhatsApp requires target <E.164|group JID>
    at resolveOutboundTarget (src/whatsapp/resolve-outbound-target.ts:XX)
    at sendWhatsAppMessage (src/whatsapp/sender.ts:XX)

Konfigurationskontext

Das Problem tritt auf, wenn die folgende Konfiguration vorhanden ist:

json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"] } } }

CLI-Diagnosebefehle

bash

Versuchen, eine Nachricht an einen nicht aufgeführten Kontakt zu senden

$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Hello”}’

Erwartet: Nachricht erfolgreich gesendet

Tatsächlich: Fehler - Delivering to WhatsApp requires target <E.164|group JID>

Sekundäres Symptom: Verwirrendes Sicherheitsmodell

Benutzer beobachten, dass das Hinzufügen eines Kontakts zu allowFrom zwei Auswirkungen hat:

  • Der Kontakt kann nun ausgehende Nachrichten vom Bot empfangen
  • Der Kontakt kann auch eingehende Nachrichten senden, die den Bot auslösen

Dies verstößt gegen das Prinzip der geringsten Privilegien und erzeugt Sicherheitsverwirrung.

🧠 Ursache

Architekturanalyse

Die Grundursache liegt in der gemeinsamen Datenabhängigkeit zwischen eingehender und ausgehender Zugriffskontrolllogik.

Datei: src/whatsapp/resolve-outbound-target.ts

typescript export async function resolveOutboundTarget( normalizedTo: string, allowList: string[] ): Promise {

const hasWildcard = allowList.includes("*");

if (hasWildcard || allowList.length === 0) { return { ok: true, to: normalizedTo }; }

if (allowList.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }

return { ok: false, error: Delivering to WhatsApp requires target <E.164|group JID>, }; }

Das Problem: Diese Funktion empfängt das allowFrom-Array als allowList-Parameter, was bedeutet, dass die ausgehende Berechtigung durch die eingehende Konfiguration kontrolliert wird.

Datei: src/web/inbound/access-control.ts

typescript export function checkInboundAccess( from: string, allowFrom: string[] ): InboundAccessResult { const hasWildcard = allowFrom.includes("*"); const isAllowed = hasWildcard || allowFrom.includes(from);

return { allowed: isAllowed, reason: isAllowed ? “allowed” : “inbound_not_authorized” }; }

Das Problem des gemeinsamen Kontrollpunkts

KonfigurationEingehende WirkungAusgehende Wirkung
"allowFrom": ["+1234567890"]Nur +1234567890 kann den Bot auslösenBot kann nur an +1234567890 senden
"allowFrom": ["*"]Jeder kann den Bot auslösenBot kann an jeden senden

Designverstoß

Die aktuelle Implementierung verstößt gegen das Prinzip der Trennung von Anliegen. Das allowFrom-Feld wurde für die eingehende Zugriffskontrolle entwickelt, wird aber für die ausgehende Autorisierung wiederverwendet, was eine unbeabsichtigte Kopplung schafft.

🛠️ Schritt-für-Schritt-Lösung

Phase 1: Konfigurationstyp hinzufügen

Datei: src/config/types.whatsapp.ts

Vorher: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; // … other fields }

Nachher: typescript export interface WhatsAppConfig { dmPolicy: “allowlist” | “open”; allowFrom: string[]; allowSendTo?: string[]; // NEU: Separate ausgehende Allowlist // … other fields }

Phase 2: Ausgehende Auflösungslogik aktualisieren

Datei: src/whatsapp/resolve-outbound-target.ts

Vorher: typescript export async function resolveOutboundTarget( normalizedTo: string, allowList: string[] ): Promise {

const hasWildcard = allowList.includes("*");

if (hasWildcard || allowList.length === 0) { return { ok: true, to: normalizedTo }; }

if (allowList.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }

return { ok: false, error: Delivering to WhatsApp requires target <E.164|group JID>, }; }

Nachher: typescript export async function resolveOutboundTarget( normalizedTo: string, sendToList: string[] | undefined, inboundAllowFrom: string[] ): Promise {

// Wenn sendTo explizit konfiguriert ist, verwende es if (sendToList !== undefined) { const hasWildcard = sendToList.includes("*");

if (hasWildcard || sendToList.length === 0) {
  return { ok: true, to: normalizedTo };
}

if (sendToList.includes(normalizedTo)) {
  return { ok: true, to: normalizedTo };
}

return {
  ok: false,
  error: `Target ${normalizedTo} is not in allowSendTo list`,
};

}

// Fallback auf Legacy-Verhalten (verwende eingehendes allowFrom für ausgehend) const hasWildcard = inboundAllowFrom.includes("*");

if (hasWildcard || inboundAllowFrom.length === 0) { return { ok: true, to: normalizedTo }; }

if (inboundAllowFrom.includes(normalizedTo)) { return { ok: true, to: normalizedTo }; }

return { ok: false, error: Delivering to WhatsApp requires target <E.164|group JID>, }; }

Phase 3: Aufruferstellen aktualisieren

Datei: src/whatsapp/sender.ts (oder wo immer resolveOutboundTarget aufgerufen wird)

Vorher: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowFrom // Übergabe der eingehenden Liste für ausgehende Prüfung );

Nachher: typescript const target = await resolveOutboundTarget( normalizedTo, config.allowSendTo, // Verwende dedizierte ausgehende Liste config.allowFrom // Übergabe für Legacy-Fallback );

Phase 4: Konfigurationsbeispiel

Empfohlene Produktionskonfiguration:

json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890", “+1111111111”], “allowSendTo”: ["*"] } } }

Strenge ausgehende Konfiguration:

json { “channels”: { “whatsapp”: { “dmPolicy”: “allowlist”, “allowFrom”: ["+1234567890"], “allowSendTo”: [ “+0987654321”, “+1122334455”, “12036301234567890-1234567890@g.us” ] } } }

🧪 Verifizierung

Testfall 1: Ausgehend an erlaubten SendTo

bash

Konfiguration

“allowSendTo”: ["+0987654321"]

$ openclaw tools call message ‘{“to”: “+0987654321”, “body”: “Test”}’

Erwartete Ausgabe: json { “ok”: true, “messageId”: “wamid.xxx…”, “timestamp”: “2024-01-15T10:30:00Z” }

Testfall 2: Ausgehend an nicht aufgeführten SendTo

bash

Konfiguration

“allowSendTo”: ["+0987654321"]

$ openclaw tools call message ‘{“to”: “+5555555555”, “body”: “Test”}’

Erwartete Ausgabe: json { “ok”: false, “error”: “Target +5555555555 is not in allowSendTo list” }

Testfall 3: Eingehend von erlaubtem Absender

bash

Konfiguration

“allowFrom”: ["+0987654321"]

“allowSendTo”: ["*"]

Nachricht VON +0987654321 AN den Bot senden

Erwartetes Verhalten: Nachricht wird verarbeitet und löst Bot-Antwort aus.

Testfall 4: Eingehend von nicht aufgeführtem Absender

bash

Konfiguration

“allowFrom”: ["+0987654321"]

Nachricht VON +5555555555 AN den Bot senden

Erwartetes Verhalten: Nachricht wird mit Zugriffskontrollfehler abgelehnt.

Testfall 5: Wildcard SendTo

bash

Konfiguration

“allowSendTo”: ["*"]

$ openclaw tools call message ‘{“to”: “+anyvalidnumber”, “body”: “Test”}’

Erwartete Ausgabe: Nachricht wird erfolgreich gesendet.

Verifizierungsskript

typescript // test/whatsapp-outbound-permissions.test.ts

import { resolveOutboundTarget } from “../src/whatsapp/resolve-outbound-target”;

describe(“resolveOutboundTarget”, () => { test(“allows when target is in sendTo list”, async () => { const result = await resolveOutboundTarget( “+0987654321”, ["+0987654321", “+1122334455”], ["+1234567890"] ); expect(result.ok).toBe(true); });

test(“blocks when target is not in sendTo list”, async () => { const result = await resolveOutboundTarget( “+5555555555”, ["+0987654321"], ["+1234567890"] ); expect(result.ok).toBe(false); expect(result.error).toContain(“not in allowSendTo list”); });

test(“allows wildcard sendTo”, async () => { const result = await resolveOutboundTarget( “+5555555555”, ["*"], ["+1234567890"] ); expect(result.ok).toBe(true); });

test(“falls back to allowFrom when sendTo is undefined”, async () => { const result = await resolveOutboundTarget( “+1234567890”, undefined, // sendTo nicht konfiguriert ["+1234567890"] ); expect(result.ok).toBe(true); }); });

⚠️ Häufige Fehler

Fehler 1: Falsches E.164-Format

WhatsApp erfordert Nummern im E.164-Format (z.B. +1234567890). Die Verwendung von Formaten ohne führendes + führt zu stillen Fehlern.

bash

FALSCH

$ openclaw tools call message ‘{“to”: “1234567890”, “body”: “Test”}’

RICHTIG

$ openclaw tools call message ‘{“to”: “+1234567890”, “body”: “Test”}’

Fehler 2: Gruppen-JID vs. Telefonnummer

Gruppen-IDs verwenden ein anderes Format als Telefonnummern. Stellen Sie die korrekte JID-Syntax sicher:

json { “allowSendTo”: [ “+1234567890”, // Telefonnummer “12036301234567890-1234567890@g.us” // Gruppen-JID ] }

Fehler 3: Leeres Array vs. Undefined

Ein leeres allowSendTo: []-Array wird anders behandelt als allowSendTo, das undefined ist:

  • "allowSendTo": [] — Blockiert alle ausgehenden Nachrichten
  • "allowSendTo": undefined — Fällt zurück auf Legacy-allowFrom-Verhalten

Fehler 4: Docker-Umgebungsvariablen-Zuordnung

Bei Verwendung von Umgebungsvariablen für die Konfiguration:

bash

FALSCH - Dies erstellt einen String, kein Array

WHATSAPP_ALLOW_SEND_TO=+1234567890,+0987654321

RICHTIG - JSON-String für Arrays verwenden

WHATSAPP_ALLOW_SEND_TO=["+1234567890","+0987654321"]

Fehler 5: Cache-Probleme

Nach dem Aktualisieren der Konfiguration muss der laufende Prozess neu laden:

bash

OpenClaw-Dienst neu starten

$ systemctl restart openclaw

Oder für Docker

$ docker-compose down && docker-compose up -d

Fehler 6: Migration von Legacy-Konfiguration

Existierende Konfigurationen ohne allowSendTo sollten über den Fallback-Mechanismus weiterhin funktionieren. Testen Sie jedoch gründlich:

typescript // Fallback-Verhalten verifizieren const sendTo = config.allowSendTo ?? config.allowFrom;

Fehler 7: Sicherheitsimplikationen von Wildcards

Das Setzen von "allowSendTo": ["*"] erlaubt das Senden an jede gültige WhatsApp-Nummer. Berücksichtigen Sie:

  • Ratenbegrenzung für das Message-Tool
  • Zusätzliche autorisierungsbasierte Anwendungsebene
  • Protokollierung aller ausgehenden Nachrichtenversuche

🔗 Zugehörige Fehler

  • E_DELIVERY_FAILED — Generischer Übermittlungsfehler, wenn die WhatsApp-API die Nachricht ablehnt
  • E_INVALID_TARGET — Zielnummerformat ist ungültig (nicht E.164-konform)
  • E_INBOUND_NOT_AUTHORIZED — Eingehende Nachricht aufgrund von Allowlist-Richtlinie abgelehnt
  • E_SESSION_NOT_READY — WhatsApp-Sitzung vor ausgehendem Versuch nicht hergestellt
  • E_ALLOWLIST_BLOCKED — Ausgehendes Ziel nicht in konfigurierter Allowlist

Zugehörige GitHub-Issues

  • Issue #XXX — WhatsApp dmPolicy Allowlist blockiert legitime ausgehende Nachrichten
  • Issue #YYY — Anfrage: Separate eingehende/ausgehende Zugriffskontrolle für WhatsApp
  • Issue #ZZZ — Dokumentation: WhatsApp-Kanal-Sicherheitsmodell unklar

Konfigurationsreferenz

  • channels.whatsapp.dmPolicy — Steuert den eingehenden Zugriffsmodus (`"allowlist"` | `"open"`)
  • channels.whatsapp.allowFrom — Eingehende Allowlist (Telefonnummern und Gruppen-JIDs)
  • channels.whatsapp.allowSendTo — Ausgehende Allowlist (Telefonnummern und Gruppen-JIDs) [NEU]

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.