{
  "name": "pingwa — approve an incoming request on WhatsApp (webhook)",
  "nodes": [
    {
      "parameters": {
        "content": "## Approve an incoming request on WhatsApp\n\n**Who it's for:** an external system (Stripe, your app, a form, another service) fires a webhook, and you want a human to tap **approve** on WhatsApp before the flow acts — then answer the caller with the decision.\n\n**What it does:** turns any incoming POST into a human-in-the-loop gate. Runs on the official Meta Cloud API through pingwa — no Twilio, no WhatsApp Business Account, no Meta credentials.\n\n**How it works (left → right):**\n1. **Incoming request** — a Webhook trigger. Point your caller at its Production URL and POST e.g. `{ \"amount\": 240, \"customer\": \"Acme\" }`.\n2. **Read the request** — pull the fields and build the summary the human will judge.\n3. **Ask on WhatsApp** — POSTs `/v1/ask` and long-polls; the flow waits here until you answer or 90 s pass.\n4. **Approved?** — routes on the tapped button.\n5. Approved → run the real action, then **Respond** approved · Rejected → **Respond** rejected · No reply → **Respond** timeout (fail-safe).\n\n**Public URL note:** only the *trigger* needs a reachable URL (that's the caller's requirement, not pingwa's). pingwa's reply still comes back on the outbound `/v1/ask` call — no inbound webhook for pingwa. Behind NAT with no public URL? Use the outbound-only variant instead: https://pingwa.dev/pingwa-ask.n8n.json\n\nDocs: https://pingwa.dev/n8n",
        "height": 500,
        "width": 640,
        "color": 5
      },
      "id": "c1c2c3c4-0000-4000-8000-000000000001",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [-400, -300]
    },
    {
      "parameters": {
        "content": "### Setup (once)\n1. Send **join** to pingwa on WhatsApp → API key (`pw_...`).\n2. **Self-hosted:** set `PINGWA_KEY` env var; nodes read `{{ $env.PINGWA_KEY }}`.\n   **n8n Cloud:** `$env` is blocked — make a **Header Auth** credential (`Authorization` = `Bearer pw_...`) and attach it to the *Ask on WhatsApp* node.",
        "height": 230,
        "width": 330,
        "color": 7
      },
      "id": "c1c2c3c4-0000-4000-8000-000000000002",
      "name": "Setup note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [280, -320]
    },
    {
      "parameters": {
        "content": "### Reply & fail-safe\n`reply.button_id`: **b0** = approve, **b1** = reject.\nNo answer → HTTP **408** → error output → **Respond** with `timeout`. **Silence never approves.**\nThe caller gets a JSON body back on every path, so it knows what happened.",
        "height": 220,
        "width": 340,
        "color": 7
      },
      "id": "c1c2c3c4-0000-4000-8000-000000000003",
      "name": "Reply note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [1020, 260]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "pingwa-approval",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000010",
      "name": "Incoming request",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [-160, 0],
      "webhookId": "d1d2d3d4-0000-4000-8000-0000000000aa"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e0e1e2e3-2222-4222-8222-000000000001",
              "name": "summary",
              "value": "=Refund €{{ $json.body.amount }} to {{ $json.body.customer }}?",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000011",
      "name": "Read the request",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [60, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://pingwa.dev/v1/ask",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.PINGWA_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: $json.summary, buttons: ['approve', 'reject'], timeout: 90 }) }}",
        "options": {
          "timeout": 95000
        }
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000012",
      "name": "Ask on WhatsApp",
      "onError": "continueErrorOutput",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [300, 0]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "e3e2e1e0-3333-4333-8333-000000000001",
              "leftValue": "={{ $json.reply.button_id }}",
              "rightValue": "b0",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000013",
      "name": "Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [540, 0]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://httpbin.org/post",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ action: 'approved' }) }}",
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000014",
      "name": "Run the approved action",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [780, -140]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'approved', button_id: $('Ask on WhatsApp').item.json.reply.button_id }) }}",
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000015",
      "name": "Respond approved",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [1020, -140]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ status: 'rejected' }) }}",
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000016",
      "name": "Respond rejected",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [780, 60]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseCode": 408,
        "responseBody": "={{ JSON.stringify({ status: 'timeout', note: 'no reply — fail-safe, not approved' }) }}",
        "options": {}
      },
      "id": "d1d2d3d4-0000-4000-8000-000000000017",
      "name": "Respond timeout",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [780, 220]
    }
  ],
  "connections": {
    "Incoming request": {
      "main": [
        [
          {
            "node": "Read the request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the request": {
      "main": [
        [
          {
            "node": "Ask on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask on WhatsApp": {
      "main": [
        [
          {
            "node": "Approved?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond timeout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Approved?": {
      "main": [
        [
          {
            "node": "Run the approved action",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run the approved action": {
      "main": [
        [
          {
            "node": "Respond approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}
