Human approval in your n8n flow: no Twilio, no WABA.
Your flow reaches the step that spends money, deletes records
or emails a customer, and you want a human to say yes first.
n8n's native WhatsApp node wants Meta or Twilio credentials, business
verification and template approval before it sends anything. pingwa is the
n8n-nodes-pingwa community node and one click: connect,
drop in the Pingwa node, your phone buzzes with buttons,
the flow waits for your tap. On Make or Node-RED (or without the node),
one HTTP request to /v1/ask does the same.
Native WhatsApp Business node
- Create a Meta developer app
- Get a WhatsApp Business Account (WABA)
- Pass business verification
- Get message templates approved
- Or route it all through Twilio
pingwa
- Install n8n-nodes-pingwa
- Connect with one click
- Drop in the Pingwa node
Honest note: if you already run a WABA and message customers, the native node is the right choice. That's what it's for. pingwa is for the other case: the flow asking you, its operator, for a decision.
The n8n community node
On n8n, skip the HTTP plumbing: n8n-nodes-pingwa is the official community node, live on npm. It adds a Pingwa node that sends a WhatsApp message or, as the human-in-the-loop step, asks a question and holds the workflow until you answer (approve or reject), plus Pingwa Trigger and Pingwa Poll Trigger to start a workflow on an inbound WhatsApp message.
- Where
- n8n → Settings → Community Nodes → Install
- Package
- n8n-nodes-pingwa
- Result
- Pingwa, Pingwa Trigger and Pingwa Poll Trigger appear in the node panel
- 1
- Add a Pingwa OAuth2 API credential and click Connect: a pingwa tab opens
- 2
- Continue with Google, or connect a number directly
- 3
- Pick which WhatsApp number n8n uses, or add a new one
- 4
- New number only: tap Open WhatsApp & verify and send the pre-filled message (or scan the QR from another phone). pingwa spots it and continues on its own
- 5
- The tab closes; n8n stores the token and reuses it from then on
Picking a number that's already verified skips step 4: you
go straight back to n8n. Prefer a key? Add a Pingwa API
credential instead and paste your pw_ key.
Without the node, or for Make & Node-RED
Everything above is also one plain HTTP call: the only route
on Make and Node-RED, and fine on n8n if you'd rather not install a community
node. Two importable n8n templates wrap the core (HTTP Request to
/v1/ask → IF on the button reply) inside a complete,
runnable workflow with the approve, reject and no-answer paths all wired.
Pick by how your flow starts:
- Download
- pingwa-ask.n8n.json
- Shape
- Schedule → Set → Ask on WhatsApp → IF → run action · WhatsApp receipt on reject/timeout
- For
- a scheduled or manual flow that reaches a risky step. The reply returns on the same outbound call, so a self-hosted n8n behind NAT needs no webhook, no public URL, no tunnel.
- Download
- pingwa-webhook-approval.n8n.json
- Shape
- Webhook → Set → Ask on WhatsApp → IF → run action → Respond to Webhook (approved / rejected / timeout)
- For
- an external system (Stripe, your app, a form) that POSTs an event and gets the decision back. Only the trigger needs a public URL (the caller's requirement, not pingwa's); the WhatsApp reply is still outbound-only.
- Import
- n8n → Workflows → ⋯ → Import from File
- Key
- set
PINGWA_KEYas an environment variable on the n8n host. On n8n Cloud,$envis blocked in expressions (it fails with an opaque “access denied” error). Create a Header Auth credential there instead (nameAuthorization, valueBearer pw_...) and attach it to the pingwa HTTP node(s).
The Ask on WhatsApp HTTP Request node, the heart of both templates, spelled out:
- Method
- POST
- URL
- https://pingwa.dev/v1/ask
- Header
- Authorization: =Bearer {{ $env.PINGWA_KEY }} (the leading = is workflow-export notation: in the UI you toggle the value field to Expression and type the rest)
- Body (JSON)
- {"text": "Approve this step?", "buttons": ["approve", "reject"], "timeout": 90}
- Timeout
- 95000 ms: a hair over the 90 s the API will hold the question open
timeout: 90 in the body is the maximum the API
accepts; the node's own 95 s timeout just makes sure n8n outlasts the
long-poll instead of cutting it off. Put anything you like in
text: an n8n expression interpolating the item being approved
works fine.
Branch on the answer
A successful response looks like this. button_id
is positional: b0 is the first button you passed, b1
the second.
{
"message_id": 123,
"billing_class": "template",
"answered": true,
"reply": {"answer_id": 124, "text": "approve", "button_id": "b0",
"created_at": "2026-07-16T14:03:07Z"}
}
So the IF node's condition is a plain string-equals: left
value {{ $json.reply.button_id }}, right value b0.
True branch = approved, false branch = rejected.
If the ask lands outside your 24h reply window (likely, for
approvals that arrive out of the blue), the buttons arrive as a numbered list
instead of tap buttons. Replying 1 (or the button's text,
approve) works the same and still maps to b0.
If nobody answers within the 90 seconds, /v1/ask
returns HTTP 408 and the HTTP Request node errors. Treat that
as a reject: silence should never approve anything. Wire the
node's error output to the same path as the false branch (enable
“Settings → On Error → Continue (using error output)”). The question was
still delivered to your phone; a late tap is retrievable afterwards via
GET /v1/messages/<id>/reply if your flow wants a second look.
Node-RED variant
Same shape: a function node prepares the request, an http request node (set to POST, “return a parsed JSON object”) makes the call, and a second function (or switch) node branches on the button.
// function node BEFORE the http request node
msg.url = "https://pingwa.dev/v1/ask";
msg.method = "POST";
msg.headers = {
"Authorization": "Bearer " + env.get("PINGWA_KEY"),
"Content-Type": "application/json"
};
msg.payload = {text: "Approve this step?", buttons: ["approve", "reject"], timeout: 90};
return msg;
// output 1 = approved, output 2 = rejected (set the node to 2 outputs)
if (msg.statusCode === 200 && msg.payload.reply.button_id === "b0") {
return [msg, null];
}
return [null, msg]; // includes the 408 nobody-answered case → fail-safe reject
Make.com
In Make, it's the generic HTTP module
(“Make a request”): POST the same JSON to /v1/ask with the same
Authorization: Bearer header, then add a Router
with a filter on reply.button_id equals b0 for the
approved route and a fallback route for everything else: rejections and
timeouts alike.
FAQ
How do I get an n8n WhatsApp approval without Twilio?
Install the n8n-nodes-pingwa community node
(Settings → Community Nodes), connect with one click, and use the
Pingwa node's Ask operation, or POST to
https://pingwa.dev/v1/ask with a plain HTTP Request node (template
above, importable). pingwa carries the message over its own official Meta
Cloud API number, so you never touch Twilio, a WABA or Meta
credentials.
Does this work with n8n's send-and-wait?
It is the send-and-wait: POST /v1/ask long-polls,
so the HTTP Request node itself holds the flow until the human answers (or
90 s pass). You don't need a separate Wait node, a webhook resume URL, or
any special node type: the one HTTP call sends the question and returns
with the answer.
What if nobody answers?
After 90 seconds /v1/ask returns HTTP 408 and the node
errors. Route the error output to your rejected path: fail-safe means
silence never approves. The question stays on the phone, and a late reply
can still be fetched with GET /v1/messages/<id>/reply.
Does it work on self-hosted n8n?
Yes: this recipe is outbound-only HTTPS from your n8n host to
https://pingwa.dev. No inbound webhook, no public URL, no tunnel
needed: the reply comes back on the same long-polled request. A homelab
n8n behind NAT works exactly like a cloud one.
Scan the code and get instant access. It opens WhatsApp with “join” ready to send, and your API key comes straight back.
Approvals are exactly the messages you want rare and deliberate. The free plan's 30 paid messages a month cover a lot of “Approve this step?”. Your button-tap replies inside the 24h window are free.