pilot-status

n8n node for Pilot Status API

Package Information

Released: this month
Downloads: 259 weeklyΒ /Β 353 monthly
Latest Version: 2.5.0
Author: Ismael Ash

Documentation

Pilot Status - n8n Integration

This is the official n8n node for Pilot Status β€” a WhatsApp transactional, marketing, and bulk messaging platform.

With this integration, you can seamlessly connect your n8n workflows to Pilot Status to send messages, connect WhatsApp numbers, and react to incoming messages or status changes in real time.

Not every endpoint is a node operation, and that is deliberate. In an n8n node the cost is not the HTTP call β€” it is the UI: every field needs a control written and positioned by hand, and every optional one needs a hidden twin to undo n8n's empty-value behaviour. So this node covers the operations a workflow author actually reaches for. For anything else in the public API β€” templates CRUD, group writes, Chatwoot, webhooks CRUD, billing β€” use n8n's built-in HTTP Request node with the same Pilot Status API credential: it is header auth, so it works out of the box. That is the supported escape hatch, not a workaround.

πŸ“¦ Installation

To install this node in your n8n instance, follow these steps:

For n8n UI (Community Nodes)

  1. Go to Settings > Community Nodes.
  2. Click on Install.
  3. Enter n8n-nodes-pilot-status.
  4. Click Install.

For Docker / Self-hosted

If you are hosting n8n yourself, you can install it via npm:

npm install n8n-nodes-pilot-status

Make sure to restart your n8n instance after installation.


πŸ”‘ Authentication

To use the Pilot Status node, you need an API Key.

  1. Go to your Pilot Status Dashboard.
  2. Create a new API Key.
  3. In n8n, add a new Pilot Status API credential and paste your API Key.

The credential has two optional fields:

  • Base URL β€” API root without /v1. Leave it alone unless you are pointing the workflow at a staging environment. It is used by the node, by the credential test and by the dropdowns (templates, groups, newsletters), which used to always hit production.
  • WhatsApp Number ID β€” sent as x-whatsapp-number-id on every request. Fill it in when your key is tenant-scoped and the operation is per-number; a number-scoped key already carries its number, and the header is ignored.

Note: TEST keys will only send messages to the WhatsApp number registered in your Pilot Status Profile.


⚑ Triggers (Webhooks)

The Pilot Status Trigger node allows your workflows to react to real-time events.

Registration: Automatic (recommended). Select the event, pick your Pilot Status API credential, and set Registration to Automatic. Activating the workflow creates the webhook subscribed to that event only β€” so unwanted events are never delivered β€” and deactivating it deletes the subscription.

Registration: Manual (default, unchanged). Nothing is created for you:

  1. Add the Pilot Status Trigger node to your workflow.
  2. Select the event you want to listen to (or select "All Events").
  3. Copy the Webhook URL provided by the node.
  4. Go to the Webhooks section in your Pilot Status Dashboard and paste the URL.

In this mode the event filter runs after delivery: the payload has already reached n8n, and non-matching events are answered 200 Ignored event.

⚠️ Verify the signature

Every delivery from a webhook that has a secret carries x-pilot-status-signature: HMAC-SHA256 hex over the raw body. Paste that secret into the trigger's Signing Secret field and any delivery that does not match is rejected with 401. Leave it empty and anyone who learns your n8n webhook URL can fire this workflow with a hand-written payload.

The API never returns the secret β€” not even in the 201 of the create call β€” so it cannot be filled in for you, including in Automatic registration. Copy it from the dashboard when the webhook is created; it is shown once.

Available Events:

  • Message Sent (message.sent): Triggered when the message is sent to the provider.
  • Message Delivered (message.delivered): Triggered when the message is successfully delivered to the device.
  • Message Read (message.read): Triggered when the user reads the message.
  • Message Failed (message.failed): Triggered if the message fails to send (includes error details).
  • Message Reply (message.reply): Triggered when a user replies specifically to an automated message.
  • Message Received (message.received): Triggered when a general inbound message is received on your connected WhatsApp number.
  • Group Message (message.group): Triggered when an inbound group message is received (group subject is included when available).
  • Newsletter Message (message.newsletter): Triggered when an inbound WhatsApp channel / newsletter message is received.
  • Number Created / Connected / Disconnected / Removed / Restored (number.created, number.connected, number.disconnected, number.removed, number.restored): Lifecycle of a WhatsApp number in your account. number.restored means a deleted number was brought back within its 30-day window β€” it returns disconnected and must be reconnected.
  • Call Ringing / Connected / Ended / Missed / Permission Updated (call.ringing, call.connected, call.ended, call.missed, call.permission_updated): WhatsApp Business Calling events (call.*) β€” Meta Cloud API and web (Pilot Status) numbers (call.permission_updated is Meta-only). Payloads are flat (fields next to event, no data wrapper): { event, callId, externalCallId?, direction, status, from, to, timestamp, duration? }; duration (seconds) appears on call.ended only when the call was answered. externalCallId is the provider call id (Meta wacid... or Evolution GO CallID).
  • Flow Response Received (flow.response_received): Triggered when someone SUBMITS a WhatsApp Flow form β€” the answers arrive here. META numbers only. Wrapped in data: { event, data: { event, flowId, metaFlowId, flowToken, messageId, whatsappNumberId, from, receivedAt, response, responseRaw, flowMedia, submitted } }. Four things to know. The number field is whatsappNumberId, not numberId β€” same value, different name on the wire. response and responseRaw are mutually exclusive: response holds what your own Flow endpoint returned, already parsed, and responseRaw is null; if the parse failed it is the other way round, and the raw string is the only copy of the answer there is. On a Flow with a data_exchange endpoint response is EMPTY even on a number that stores everything β€” Meta's completion payload carries only the flow_token β€” and what the person typed is in submitted: one entry per data_exchange screen, { screen, at, data, omitted }, in order, where omitted (DATA_TOO_LARGE, LIMIT_REACHED or UNREADABLE) says why a data is null. flowMedia holds the files attached inside the form: { field, fileName, mimeType, status, url }. Both are null and never [] when empty, so a node testing for an empty array never fires; GET /v1/flows/{id}/responses publishes the same two lists as arrays, under media and submitted. And picking this event for a non-Meta number fails SILENTLY: the API filters a webhook's events down to what the number's provider can emit, so the subscription is created with an empty event list, returns 201, and never fires.
  • Lead Received (lead.received): A lead from a Meta Lead Ads Instant Form (Facebook / Instagram), delivered on the WhatsApp number whose Page is linked in the dashboard β€” any provider, not only Meta numbers. Wrapped in data: { event, data: { leadId, leadgenId, whatsappNumberId, pageId, pageName, formId, formName, adId, adsetId, campaignId, adName, adsetName, campaignName, platform, isOrganic, createdAt, fullName, email, phone, fields, consentGiven, conversationId } }. The number field is whatsappNumberId, not numberId. leadId is the Pilot Status id (the one Lead β†’ Get takes); leadgenId is Meta's, unique per lead β€” use it to deduplicate. createdAt is when the person submitted the form. fields holds every answer as { key, label?, values }, and values is always a list, even for a single answer. label is absent when it could not be resolved; on Lead β†’ Get / Get Many it is always present, falling back to the key. consentGiven is true/false only when the form has a consent checkbox configured; null means unknown, never a refusal.

Webhook payload notes:

  • Customer webhook payloads do not include: projectSlug, lastMessageId. Optional correlationId (same as HTTP 202 when present) may appear on outbound status events and on message.reply / message.received when correlated to a prior send.
  • message.reply always includes messageRepliedId: Pilot Status internal id of the original outbound message when correlated, or null if none was found β€” use quotedMessageId with message.sent’s messageId when it is null.
  • message.received includes fromMe (boolean) and fromName (string, optional).
  • For outbound status events (message.sent, message.delivered, message.read, message.failed): messageId is the WhatsApp provider id (may be null) and internalMessageId is the Pilot Status message id.
  • Events newer than this node version never break a workflow. The trigger does not validate the body against a list of known events: with All Events selected, an event added to the platform after your node version still fires the workflow with the body untouched (branch on event and ignore what you do not handle); with a specific event selected, anything else is answered 200 Ignored event.

πŸ›  Actions (Operations)

The Pilot Status node provides several resources and operations to manage your account and send messages.

1. Message

  • Send: Send a template message (OTP, Utility, or Marketing). Choose Phone Number or WhatsApp Group; for groups use groupId (JID ending in @g.us) instead of destinationNumber. Optional dropdown loads groups from GET /v1/groups when the WhatsApp session is connected. Variables can be provided via JSON or the UI collection (including placeholders used in button labels).
  • Get Status: Retrieve the current status of a specific message using either its Pilot Status message id or WhatsApp provider id.
  • Get History: Read the conversation history of the number, newest first β€” both directions, every provider, including messages imported from the device on connect. Filters by Start Date / End Date over the provider timestamp (when the message happened, not when it was ingested). This is how you fetch old messages: the webhook does not replay history by default. Requires a number-scoped API key.
  • Get Many: The message log β€” sent and received, with delivery status, newest first. Different from Get History: this is the operational trail (status, errorCode, origin, whether the 24h window was open at send), not the chat thread. Filters: Direction, Status (comma-separated), Page, Page Size. For date/search/template filters use the HTTP Request node.
  • Get Unread: Unread inbound messages. ⚠️ total is the real unread count from the conversation counters β€” not the length of the returned list, which carries only the latest unread per conversation.
  • Mark as Read: Blue ticks for a contact's last inbound message and every earlier one, without sending anything. Best-effort: a 200 with acked: false means there was nothing to acknowledge, or the number is RELAY_ONLY.
  • Send Typing Indicator: Show "typing"/"recording" to a contact. It clears when a message is sent or after a provider timeout (~25s on Meta). On META numbers recording is shown as typing and paused is a no-op β€” read effectiveState in the response for what actually happened.
  • Cancel: Cancel scheduled (not-yet-sent) messages by id.

2. Number

⚠️ Delete changed meaning. It used to destroy the number and free the phone for immediate reconnection. It is now a 30-day soft delete: the device is logged out, the row survives, and the response carries restorableUntil. Use Restore to undo it (you must reconnect afterwards) or Purge for the old, irreversible behaviour. Deleting still does not reduce paid capacity β€” the freed slot stays available to reconnect another number this cycle.

  • Create: Provision a new WhatsApp number instance. It returns a base64 QR Code and optional pairingCode (letter code) for WhatsApp β€œlink a device”. By default, the number is linked to the API Key used in the request.
  • Connect (QR + pairing code): Regenerate QR and pairing code for an existing instance (only when not OPEN).
  • Get Status: Check the connection status of a WhatsApp instance (e.g., CONNECTING, OPEN).
  • Get: Full detail of one number, including the settings block and β€” for official Meta numbers β€” the nested meta block.
  • Delete: Soft-delete (30 days, reversible β€” see the warning above).
  • Restore: Undo a soft-delete inside the window. Gives back the row, not the connection: run Connect afterwards (requiresReconnect: true).
  • Purge: Irreversible hard delete. Cascades chat, messages, templates and keys, and tears the number down at Meta.
  • Logout: Unpair the device without deleting anything β€” id, keys, webhooks and history are kept.
  • Check on WhatsApp: Whether a phone exists on WhatsApp. Non-official (Evolution) numbers only β€” a Meta number answers 400 NOT_SUPPORTED_FOR_META. For Brazilian mobiles the 9th-digit variant is probed too, and the returned number is the registered form to send to, which may differ from what you passed.
  • Update Settings: Per-number settings. This is the only settings endpoint: there is no POST /numbers/{id}/settings, and Evolution's syncFullHistory does not exist here. Partial β€” only the fields you add to the collection change.
    • History Import Enabled / Webhook Historical Messages apply to every provider. The second is off by default: each reconnect replays up to 30 days of conversation, and your integration cannot tell it apart from new traffic (on the FREE plan it also consumes inbound quota). To read history, use Message β†’ Get History.
    • Always Online, Reject Call, Reject Call Message, Read Messages, Ignore Groups, Ignore Status are the Evolution GO advanced settings, in the GO dialect (ignoreGroups, not the v2 groupsIgnore). They are tri-state: Inherit From Provider leaves the field untouched, Reset to Provider Default clears the stored value. On a Meta number they are stored but never applied.

3. API Key

  • Regenerate: Regenerate the default API Key of a single WhatsApp number (tenant-scoped), identified by its whatsappNumberId. The previous key for that number is rotated and the new key is returned.
  • Get Many: List API Keys. With a tenant key, this reveals every number's real API key.

4. Analytics

  • Get Dashboard: Retrieve your messaging statistics (Total Sent, Total Failed, Failure Rate, and Daily charts) based on your timezone.

6. Call (WhatsApp Business Calling β€” Meta Cloud API + web/Pilot Status numbers)

  • Initiate: Start a business-initiated voice call (BIC) β€” provide the destination phone. On Meta numbers also provide the SDP offer (RFC 8866) from your WebRTC client and a prior user call permission; BIC minutes are billed by Meta directly on your WABA (per minute, 6-second pulses, only when answered). On web (Pilot Status) numbers leave the SDP empty β€” media is handled server-side, no permission step and no Meta billing. Pilot Status does not charge for calls.
  • Accept: Answer an inbound (user-initiated) call, after the call.ringing webhook. On Meta numbers pass the business-side SDP answer, built from the sdpOffer that Get returns with Include SDP; on web numbers leave it empty.
  • Reject: Decline an incoming call.
  • Get: One call. Turn on Include SDP to also get sdpOffer/sdpAnswer β€” always null on web numbers.
  • Get Many: The number's calls, newest first (Limit, Before cursor).
  • Terminate: Hang up an active call by its Pilot Status id (call_...) or provider call id (Meta wacid / Evolution GO CallID).
  • Play (web numbers only): Play an audio file into an active call by public URL (.mp3/.wav/.opus). Queued and played on connect when the call is not active yet. Meta numbers return 400 FEATURE_NOT_SUPPORTED.
  • Realtime Session (web numbers only): Open a short-lived full-duplex realtime media session β€” returns { wsUrl, token, expiresInSeconds }. Connect a WebSocket to wsUrl and exchange raw binary PCM16 LE audio frames (plain WebSocket transport, NOT WebRTC; single-use token, ~2 minutes). talk sends and receives audio; listen only receives.
  • Get Settings / Update Settings (Meta only): Read the number's calling settings object or enable/disable calling.
  • Get Permission / Request Permission (Meta only): Check whether the business may call a user, and send the interactive call-permission request (inside the 24h service window). The user's reply arrives as the call.permission_updated webhook.

5. Service Window

  • Get: Whether the Meta 24h customer-service window is open for a contact. ⚠️ Branch on windowType, not on open: on a META number windowType is META_24H and open is a real constraint (outside the window only an approved template goes through); on a web/unofficial number windowType is NONE and open is always true, because the Meta rule does not exist there.

8. Lead (Meta Lead Ads β€” Facebook / Instagram Instant Forms)

Read-only, and requires a number-scoped API key: a lead belongs to the number whose Page is linked in the dashboard.

  • Get Many: GET /v1/leads β€” the number's leads, newest first by the time the person submitted the form. Filters: Form ID, Ad ID, Page ID (exact match), Start Date / End Date (Meta's created_time, not when Pilot Status received the lead), Page, Page Size (max 100). Answers { leads, total, page, pageSize, totalPages, notice? }.
  • Get: GET /v1/leads/{id} β€” one lead by its Pilot Status id (= data.leadId on the lead.received webhook, not Meta's leadgenId). Answers { lead, notice? }; a lead of another number is 404 LEAD_NOT_FOUND.

Each lead: { id, leadgenId, whatsappNumberId, pageId, pageName, formId, formName, adId, adName, adsetName, campaignName, platform, isOrganic, createdAt, fullName, email, phone, fields, consentGiven, conversationId, autoSendStatus, autoSendReason, source, redacted }, with fields as { key, label, values }. ⚠️ consentGiven: null means unknown (no consent checkbox configured, or not answered) β€” never read it as a refusal. On a RELAY_ONLY number a lead keeps its ids, attribution and status but comes back with redacted: true and no name, e-mail, phone or answers, and the response carries notice: "PII_RELAY_ONLY".

7. Phone Line (identity verification + activation audio)

Requires a workspace (tenant-scoped) API key β€” a number-scoped key is refused. The verification operations need the OWNER role (phone_lines:purchase); the audio needs phone_lines:read. Errors come back as { error, code }.

  • Get Verification: The workspace verification state β€” status (NONE, PENDING_CONTACT, NEEDS_DOCUMENT, IN_REVIEW, APPROVED, REJECTED), canPurchase, and verification.contact.requiredChannels / confirmedChannels / pendingChannels (UPPERCASE there: EMAIL, WHATSAPP). Personal data comes back masked.
  • Start Verification: POST /v1/phone-lines/verification with Document Type (CPF/CNPJ), Document Number, Contact Email, Contact WhatsApp (with country + area code) and the document (PDF/JPG/PNG/WEBP, up to 10 MB). Codes go out to the required channels on their own.
    • Idempotency Key (required) is sent as the Idempotency-Key header. ⚠️ Reuse the same key when retrying the same submission β€” the API then answers with the verification it already created (200, Idempotent-Replayed: true) instead of starting another. Map it from your own data (e.g. an id of the input item); a key that changes on every run makes retries unsafe. Same key + different body β†’ 409 IDEMPOTENCY_KEY_REUSED; while the first request is still running β†’ 409 IDEMPOTENCY_KEY_IN_USE.
    • While PENDING_CONTACT, a new start replaces the current one (the old codes stop working). Limit: 10 submissions per hour per workspace (429 RATE_LIMITED).
  • Send Contact Code: Resend a code on a channel (whatsapp or email). Codes expire in 10 minutes; 60 s cooldown per channel (429 CONTACT_CODE_COOLDOWN).
  • Confirm Contact: Confirm a channel with the received Code. The last required channel may move the verification to APPROVED or IN_REVIEW. 5 wrong attempts lock the code (429 CONTACT_CODE_TOO_MANY_ATTEMPTS).
  • Submit Document: Send a new document when document.status is UNREADABLE/UNCLEAR or the status is NEEDS_DOCUMENT. Same file rules and hourly limit as the start; no idempotency key.
  • Download Activation Audio: The recording of an activation call (activationId from the phone_line.audio_received / phone_line.code_received webhook), returned as n8n binary data in the field set by Put Output File in Field (default data), with the MIME type from Content-Type and the file name from Content-Disposition. The JSON part carries only { activationId, contentType, contentLength, fileName }. β›” The recording speaks the activation code β€” treat the file as a credential (do not post it to chats or logs). An unknown id, a line from another workspace, a missing recording or a returned/canceled line all answer 404 ACTIVATION_NOT_FOUND.

Document input β€” two options (Document Source):

  • Binary Field (default): the name of a binary property of the input item (e.g. the output of Read/Write Files from Disk, HTTP Request or a form upload). It is sent as data:<mimeType>;base64,<data>.
  • Data URI: a ready data:<mime>;base64,... string.

Phone-line webhooks (phone_line.*) live in a separate registry with their own plwh_... secret, and the Pilot Status Trigger does not register them: its Automatic mode creates subscriptions in the numbers webhook registry, which never delivers phone_line.*. To receive them, create the phone-line webhook in the dashboard and point it at a regular n8n Webhook node (verify x-pilot-status-signature, HMAC-SHA256 of the raw body).


πŸ’‘ Use Case Examples

Here are some ideas of what you can build with this integration:

  1. Abandoned Cart Recovery: Trigger a workflow from your e-commerce platform (like Shopify or WooCommerce) to send a Pilot Status message with a discount code 1 hour after cart abandonment.
  2. Customer Support Routing: Use the Trigger node to listen for Message Received. If the customer asks for help, create a ticket in Jira or Zendesk automatically.
  3. SaaS Onboarding: When a new client signs up on your app, automatically generate an API Key and provision a WhatsApp Number for them using n8n.
  4. Appointment Reminders: Connect Google Calendar to n8n and use the deliverAt field in the Send Message operation to schedule a reminder 24 hours before the event.

πŸ“š Documentation & Support

If you have any questions or need help, feel free to reach out to our support via the dashboard!

Discussion