Skip to content
// Turn a WhatsApp Cloud API webhook into one item per incoming message.
// Accepts the raw webhook body (Inbound API) or the WhatsApp Trigger's items (one per change.value).
const viaApi = $('Inbound API').isExecuted;
const q = viaApi ? $('Inbound API').first().json.query ?? {} : {};
const values = viaApi
  ? ($('Inbound API').first().json.body?.entry ?? []).flatMap((e) => e.changes ?? []).map((c) => c.value ?? {})
  : $('WhatsApp Trigger').all().map((i) => i.json);

const textOf = (m) =>
  m.text?.body ??
  m.interactive?.button_reply?.title ??
  m.interactive?.list_reply?.title ??
  m.button?.text ??
  m[m.type]?.caption ??
  '';

const out = [];
for (const v of values) {
  const names = Object.fromEntries((v.contacts ?? []).map((c) => [c.wa_id, c.profile?.name ?? '']));
  for (const m of v.messages ?? []) {
    out.push({
      json: {
        wa_id: String(m.from ?? ''),
        profile_name: names[m.from] ?? '',
        message_id: String(m.id ?? ''),
        type: m.type ?? 'unknown',
        text: String(textOf(m)).trim().slice(0, 2000),
        received_at: m.timestamp ? new Date(Number(m.timestamp) * 1000).toISOString() : new Date().toISOString(),
        phone_number_id: v.metadata?.phone_number_id ?? '',
        dry_run: q.dry_run === 'true',
        now: q.now || new Date().toISOString(), // tests inject the clock with ?now=
        via_api: viaApi,
      },
    });
  }
}
// Status updates (sent/delivered/read) have no messages: return one marker item so the API caller gets an answer.
return out.length ? out : [{ json: { ignored: true, reason: 'no messages (status update or other event)', via_api: viaApi } }];