Working with an AI agent? Download this documentation as a Markdown file to use as context.
Download .mdUnified REST API for WhatsApp, Instagram and Facebook Messenger.
| Base URL | /api/v1 |
| Format | JSON |
| Auth | X-API-Key header |
| n8n nodes | n8n-nodes-fiwano on npm |
All three channels are connected the same way (OAuth). The table below shows what each channel supports.
| Feature | Facebook Messenger | ||
|---|---|---|---|
| Outbound messages | Text (for media, use templates with media headers) | Text only | Text only |
| Template messages | ✅ Required outside 24h window | ❌ Not supported | ❌ Not supported |
| Incoming webhooks | text; all others as unsupported with unsupported_type |
text; media as unsupported |
text; attachments as unsupported |
| Delivery statuses | sent delivered read failed |
delivered read |
delivered read |
| Recipient format | Phone number without + (e.g. 1234567890) |
IGSID — from data.from in webhooks |
PSID — from data.from in webhooks |
| 24h window workaround | Use approved templates | None — wait for user to message | None — wait for user to message |
| Channel identifier | phone_number_id |
ig_account_id |
page_id |
| Sender profile | data.from_name (from Meta contacts) |
Via profile endpoint | Via profile endpoint |
Note: Each Meta account (phone number, Instagram account, or Facebook Page) can only be connected to one Fiwano user at a time.
All API requests require an API key in the X-API-Key header.
Create a key from the API Keys page in the portal. The full key is shown only once — save it securely. Lost keys cannot be recovered; revoke and create a new one.
curl -H "X-API-Key: mip_live_abc123..." \
https://fiwano.com/api/v1/channels
All keys start with mip_live_. Keys are hashed on our side.
A webhook_secret is auto-generated when you first set a webhook URL. Use it to verify incoming webhook signatures.
Use this when your application connects channels on behalf of your end users.
Step 1. Register redirect URIs — whitelist the URL(s) where users will be redirected after OAuth:
POST /api/v1/redirects
{ "uri_pattern": "https://yourapp.com/callback" }
Step 2. Request a setup URL (valid for 10 minutes):
POST /api/v1/channels/setup-url
{
"channel_type": "whatsapp",
"redirect_uri": "https://yourapp.com/callback"
}
Response contains setup_url — open it in a browser or popup for the user.
Step 3. User completes Meta OAuth — after approval, they are redirected to your redirect_uri with a one-time code parameter:
https://yourapp.com/callback?code=abc123...
If the user cancels: ?error=access_denied
Step 4. Exchange the code + configure webhook (within 5 minutes, single-use):
POST /api/v1/channels/exchange-code
{
"code": "abc123...",
"webhook_url": "https://yourapp.com/webhooks/meta",
"webhook_secret": "your-optional-secret",
"webhook_events": ["message.received", "message.delivered", "message.failed"]
}
Returns channel_id, channel details, and webhook_secret (auto-generated if not provided). All fields except code are optional — you can set them later via PATCH /api/v1/channels/{id}.
Important: By default, no events are delivered. You must explicitly set webhook_events to receive webhooks.
List all channels for your account (both active and inactive).
Response:
{
"channels": [
{
"id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"name": "My Business",
"is_active": true,
"phone_number_id": "123456789",
"phone_number": "+1234567890",
"waba_id": "987654321",
"webhook_url": "https://yourapp.com/webhooks",
"has_webhook_secret": true,
"connected_at": "2025-01-15T10:30:00",
"token_expires_at": "2025-03-15T10:30:00"
}
],
"total": 1
}
Response fields:
| Field | Description |
|---|---|
id |
Channel ID (use in all other API calls) |
channel_type |
whatsapp, instagram, or facebook |
name |
Display name (business name, username, or page name) |
is_active |
true if the channel can send/receive messages |
phone_number_id |
WhatsApp only — Meta's phone number ID |
phone_number |
WhatsApp only — human-readable phone number |
waba_id |
WhatsApp only — WhatsApp Business Account ID |
ig_account_id |
Instagram only — Instagram account ID |
ig_username |
Instagram only — Instagram username |
page_id |
Instagram/Facebook — linked Facebook Page ID |
webhook_url |
Where incoming messages are delivered |
has_webhook_secret |
Whether a webhook secret is configured |
webhook_events |
List of enabled event types (e.g. ["message.received", "message.delivered"]) |
token_expires_at |
When the access token expires (auto-refreshed 7 days before) |
Get details of a specific channel. Same response format as the list endpoint.
Generate an OAuth URL to connect a new channel. See API connection flow above.
| Field | Type | Required | Description |
|---|---|---|---|
channel_type |
string | Yes | whatsapp, instagram, or facebook |
redirect_uri |
string | Yes | Must be in your allowed redirect URIs |
Response:
{
"setup_url": "https://fiwano.com/setup/start?session=...",
"session_id": "e7f8a1b2c3d45678",
"expires_at": "2025-01-15T10:40:00"
}
The setup URL is valid for 10 minutes.
Exchange a one-time completion code for channel data. Optionally configure webhook in the same call.
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | Yes | One-time code from the OAuth redirect. Expires in 5 minutes, single-use. |
webhook_url |
string | No | HTTPS URL for incoming webhooks. If provided, webhook is configured automatically. |
webhook_secret |
string | No | Custom HMAC secret. If webhook_url is set and this is omitted, a secret is auto-generated. |
webhook_events |
string[] | No | List of event types to deliver. See Event Types for available values per channel type. If omitted, no events are delivered until configured. |
WhatsApp response:
{
"channel_id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"name": "My Business",
"phone_number_id": "123456789",
"phone_number": "+1234567890",
"webhook_url": "https://yourapp.com/webhooks",
"webhook_secret": "a1b2c3d4e5f6...",
"webhook_events": ["message.received", "message.delivered", "message.failed"]
}
Instagram response:
{
"channel_id": "b2c3d4e5f6789012",
"channel_type": "instagram",
"name": "mybusiness",
"ig_account_id": "17841400123456",
"ig_username": "mybusiness",
"webhook_url": "https://yourapp.com/webhooks",
"webhook_secret": "a1b2c3d4e5f6...",
"webhook_events": ["message.received", "message.delivered"]
}
Update channel webhook settings.
| Field | Type | Required | Description |
|---|---|---|---|
webhook_url |
string | No | HTTPS URL for incoming webhooks |
webhook_secret |
string | No | Custom HMAC secret. If omitted and no secret exists, one is auto-generated. |
webhook_events |
string[] | No | List of event types to deliver. Only events valid for the channel type are accepted. Empty array disables all events. |
Example — enable only incoming messages and failures:
{
"webhook_events": ["message.received", "message.failed"]
}
Response includes the current webhook_events list.
Deactivate a channel (soft delete). The channel stops sending and receiving messages. You can reconnect the same Meta account later via a new OAuth flow.
Send a text message through a connected channel. Works for all channel types.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id |
string | Yes | Channel to send from |
recipient |
string | Yes | See recipient format in channel capabilities table |
text |
string | Yes | Message text |
WhatsApp example:
{
"channel_id": "a1b2c3d4e5f67890",
"recipient": "1234567890",
"text": "Hello! Your order is ready."
}
Instagram example:
{
"channel_id": "b2c3d4e5f6789012",
"recipient": "6543217890123456",
"text": "Thanks for reaching out!"
}
Response:
{
"success": true,
"message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
The message_id (UUID) is used in all subsequent delivery status webhooks.
WhatsApp only. Send a template message to initiate conversations outside the 24-hour window. Only templates with status APPROVED can be sent.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id |
string | Yes | WhatsApp channel ID |
template_name |
string | Yes | Template name (e.g. order_confirmation) |
language |
string | Yes | Language code (e.g. en_US) |
recipient |
string | Yes | Phone number without + |
variables |
object | No | Variable values keyed by component type. Omit if no variables. |
Positional variables example:
{
"channel_id": "a1b2c3d4e5f67890",
"template_name": "order_confirmation",
"language": "en_US",
"recipient": "1234567890",
"variables": {
"header": ["Summer Sale"],
"body": ["Pablo", "ORD-123", "25%"],
"buttons": [{"index": 0, "value": "promo25"}]
}
}
Named variables example:
{
"channel_id": "a1b2c3d4e5f67890",
"template_name": "welcome_message",
"language": "en_US",
"recipient": "1234567890",
"variables": {
"body": {
"customer_name": "Pablo",
"order_number": "ORD-123"
}
}
}
Template without variables — omit variables entirely.
WhatsApp requires pre-approved message templates to start conversations outside the 24-hour customer service window. Templates are submitted to Meta for review (typically < 24 hours). This section only applies to WhatsApp channels.
Template lifecycle: Create → PENDING (under Meta review) → APPROVED (can be sent) or REJECTED (fix and resubmit).
List templates for a WhatsApp channel. By default, syncs with Meta before returning.
| Query param | Default | Description |
|---|---|---|
sync |
true |
Sync from Meta before returning. Set false for cached data (faster). |
status |
all | Filter: APPROVED, PENDING, REJECTED |
Get template details including components and variable definitions.
Create a new template (submitted to Meta for review, starts as PENDING).
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Lowercase alphanumeric + underscores, max 512 chars |
category |
string | Yes | MARKETING, UTILITY, or AUTHENTICATION |
language |
string | Yes | Language code (e.g. en_US, ru, es) |
components |
array | Yes | Template components. BODY required. HEADER (text only), FOOTER, BUTTONS optional. |
parameter_format |
string | No | positional (default: {{1}}, {{2}}) or named ({{customer_name}}) |
Example:
{
"name": "order_confirmation",
"category": "UTILITY",
"language": "en_US",
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} is confirmed.",
"example": {"body_text": [["Pablo", "ORD-123"]]}
}
],
"parameter_format": "positional"
}
The example field is required by Meta for review.
Update template components. All components are replaced entirely (no partial update).
| Field | Type | Required | Description |
|---|---|---|---|
components |
array | Yes | New components (replaces all existing) |
category |
string | No | New category (only for REJECTED or PAUSED templates) |
Restrictions: Approved templates can be edited max 10 times per 30 days, and only once per 24 hours. After editing, the template goes back to
PENDINGfor re-review.
Delete a template.
| Query param | Default | Description |
|---|---|---|
all_languages |
false |
Delete all language versions of this template |
Warning: After deleting an
APPROVEDtemplate, you cannot create a new template with the same name for 30 days (Meta restriction).
When a message arrives on your connected channel, we deliver it to your channel's webhook_url as a POST request with a JSON body. Each delivery is signed with your channel's webhook_secret.
Every webhook request includes an X-Webhook-Signature header:
X-Webhook-Signature: sha256=<hmac_hex>
To verify: compute HMAC-SHA256 of the raw request body using your webhook_secret as the key, then compare the hex digest.
import hmac, hashlib
def verify_signature(body: bytes, secret: str, header: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
received = header.replace("sha256=", "")
return hmac.compare_digest(expected, received)
Each channel type supports a specific set of webhook events. Only events you explicitly enable via webhook_events are delivered. By default, no events are enabled — you must configure them after connecting a channel.
| Event | Description | Channels |
|---|---|---|
message.received |
Incoming message from a user | All |
message.sent |
Your message was accepted by Meta | |
message.delivered |
Message delivered to recipient's device | WhatsApp, Instagram, Facebook |
message.read |
Message read by recipient * | WhatsApp, Instagram, Facebook |
message.failed |
Message delivery failed |
* message.read for WhatsApp depends on recipient's privacy settings — if read receipts are disabled, the read status will never arrive. Treat delivered as a terminal success state.
Available events per channel type:
| Channel | Available events |
|---|---|
message.received, message.sent, message.delivered, message.read, message.failed |
|
message.received, message.delivered, message.read |
|
message.received, message.delivered, message.read |
When you send a message via /api/v1/messages/send, you receive a message_id (UUID). All subsequent status webhooks reference this same UUID.
message_id is always present in all status events — it's a UUID generated by Fiwano, not a Meta internal ID.sent → delivered → read. Each status implies all previous ones.data.recipient is the user identifier: phone number (WhatsApp), IGSID (Instagram), or PSID (Facebook).message.read webhook for each unread message — not just the latest one.All payloads share the same top-level structure:
{
"event": "message.received",
"channel_id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"timestamp": "2025-01-15T10:30:00Z",
"data": { ... }
}
{
"event": "message.received",
"channel_id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"message_id": "wamid.xxx",
"from": "1234567890",
"from_name": "John Doe",
"type": "text",
"text": "Hello!"
}
}
data.from — sender's phone number without +. Use directly as recipient when replying.
{
"event": "message.received",
"channel_id": "b2c3d4e5f6789012",
"channel_type": "instagram",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"message_id": "mid.xxx",
"from": "6543217890123456",
"from_name": null,
"type": "text",
"text": "Hi there!"
}
}
data.from — IGSID. Use as recipient when replying. from_name is always null (Meta does not include sender name in IG webhooks).
{
"event": "message.received",
"channel_id": "c3f8a1b2e4d56789",
"channel_type": "facebook",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"message_id": "mid.xxx",
"from": "7890123456789012",
"from_name": null,
"type": "text",
"text": "Hello from Messenger!"
}
}
data.from — PSID. Use as recipient when replying. from_name is always null (Meta does not include sender name in FB webhooks).
{
"event": "message.received",
"channel_id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"message_id": "wamid.xxx",
"from": "1234567890",
"from_name": "John Doe",
"type": "unsupported",
"unsupported_type": "image"
}
}
Non-text messages (image, video, audio, document, location, sticker, etc.) are delivered with type: "unsupported" and the original type in unsupported_type.
{
"event": "message.read",
"channel_id": "b2c3d4e5f6789012",
"channel_type": "instagram",
"timestamp": "2025-01-15T10:30:10Z",
"data": {
"message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "read",
"recipient": "6543217890123456"
}
}
Same format for all channels and all statuses (sent, delivered, read). message_id is the UUID from the send response.
{
"event": "message.failed",
"channel_id": "a1b2c3d4e5f67890",
"channel_type": "whatsapp",
"timestamp": "2025-01-15T10:30:05Z",
"data": {
"message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"recipient": "1234567890",
"status": "failed",
"error": "Message undeliverable",
"errors": [{"code": 131047, "title": "Message undeliverable"}]
}
}
If your webhook URL returns a non-2xx status or is unreachable, the system retries automatically:
Important: Your endpoint must respond with HTTP 2xx within 5 seconds. Non-2xx responses or timeouts trigger the retry queue. Incoming messages from Meta are always saved on our side, even if relay fails.
Tip: Only enable the webhook events you actually handle. Unhandled events that receive non-2xx responses will fill your retry queue unnecessarily.
Sender name is not included in webhook payloads for Instagram and Facebook (data.from_name is null). To get sender profile data, use the dedicated profile endpoint — GET /api/v1/channels/{channel_id}/profile/{user_id}.
For WhatsApp, data.from_name is always present in webhooks (Meta includes it in the payload). No separate API call needed.
Fetch sender profile data from Meta Graph API. Use this to get the name, profile picture, and other details of users who message your channel.
data.from_name).username, name, profile_pic, follower_count, is_verified.first_name, last_name, profile_pic.Results are cached for 5 minutes on our side. The cached field in the response indicates whether the result was served from cache.
Tip: Call this endpoint once when you first see a new
data.fromvalue, then cache the result on your side. No need to call it on every message.
| Parameter | In | Description |
|---|---|---|
channel_id |
path | Channel ID |
user_id |
path | Sender identifier — IGSID (Instagram) or PSID (Facebook) from data.from in webhooks |
Instagram example:
curl -H "X-API-Key: mip_live_abc123..." \
https://fiwano.com/api/v1/channels/b2c3d4e5f6789012/profile/6543217890123456
Response (Instagram):
{
"channel_id": "b2c3d4e5f6789012",
"channel_type": "instagram",
"user_id": "6543217890123456",
"profile": {
"username": "johndoe",
"name": "John Doe",
"profile_pic": "https://scontent.cdninstagram.com/...",
"follower_count": 1500,
"is_verified_user": false
},
"cached": false
}
Response (Facebook):
{
"channel_id": "c3f8a1b2e4d56789",
"channel_type": "facebook",
"user_id": "7890123456789012",
"profile": {
"first_name": "John",
"last_name": "Doe",
"profile_pic": "https://platform-lookaside.fbsbx.com/..."
},
"cached": true
}
If the profile is unavailable (private account, invalid ID):
{
"channel_id": "b2c3d4e5f6789012",
"channel_type": "instagram",
"user_id": "6543217890123456",
"profile": null,
"cached": false
}
| HTTP Status | Meaning |
|---|---|
200 |
Profile returned (may be null if unavailable) |
400 |
WhatsApp channel (not supported), inactive channel, or expired token |
404 |
Channel not found |
502 |
Meta API error (retry may help) |
Manage allowed redirect URIs for your API key. Required for the programmatic channel connection flow.
List allowed redirect URIs for the current API key.
Add an allowed redirect URI.
| Field | Type | Required | Description |
|---|---|---|---|
uri_pattern |
string | Yes | HTTPS URL or wildcard pattern (e.g. https://*.example.com/callback) |
Remove an allowed redirect URI.
| Code | Meaning | What to do |
|---|---|---|
200 |
Success | — |
201 |
Created | — |
400 |
Bad request | Check the detail field |
401 |
Unauthorized | Check your X-API-Key header |
402 |
Payment required | Trial ended or subscription inactive — go to Billing |
404 |
Not found | Resource doesn't exist or belongs to another account |
429 |
Rate limit exceeded | Wait and retry. Limit: 100 req/min per API key. |
502 |
Meta API error | Upstream failure. Check detail. Retry may help. |
{ "detail": "Human-readable error description" }
100 requests per minute per API key. HTTP 429 when exceeded. Meta has its own per-channel limits (shown in Meta Business Manager, not controlled by Fiwano).
Official Fiwano community nodes for n8n are available on npm: n8n-nodes-fiwano.
Use them to build WhatsApp, Instagram and Facebook Messenger automations, AI agent workflows, and chatbots without writing custom API integration code.
npm install n8n-nodes-fiwano
Or from n8n UI: Settings → Community Nodes → Install → enter n8n-nodes-fiwano.
| Node | Type | Description |
|---|---|---|
| Fiwano | Action | Send messages, manage channels, WhatsApp templates, contact profile enrichment, redirect URIs |
| Fiwano Trigger | Webhook Trigger | Receive incoming messages and delivery status webhooks with built-in HMAC signature verification |
| Resource | Operations |
|---|---|
| Message | Send Text, Send Template (WhatsApp) |
| Channel | Get Many, Get, Generate OAuth URL, Exchange OAuth Code, Update Webhook, Delete |
| Contact | Get Profile (Instagram, Facebook — returns name, profile picture, follower count) |
| Template | Get Many, Get, Create, Delete (WhatsApp only) |
| Redirect URI | Get Many, Add, Delete |
Starts your workflow for any of these events (filter by event type in node settings):
| Event | Channels |
|---|---|
message.received |
WhatsApp, Instagram, Facebook |
message.delivered |
WhatsApp, Instagram, Facebook |
message.read |
WhatsApp, Instagram, Facebook |
message.sent |
|
message.failed |
HMAC-SHA256 signature verification is built in — the trigger validates every webhook automatically.
A ready-to-import demo workflow is included in the GitHub repository covering every operation: echo bot with profile enrichment, channel management, template CRUD, text and template messaging.
Token lifecycle — Meta tokens expire ~60 days after connection. Fiwano automatically refreshes them 7 days before expiry. If auto-refresh fails (e.g. user revoked permissions in Meta), the channel goes inactive and needs reconnection via OAuth.
Channel uniqueness — Each Meta account can only be connected to one Fiwano user at a time. HTTP 400 with "Already connected to another account" if it's already connected by someone else.
Billing — New accounts get a 7-day free trial. After expiry, sending and webhook relay are blocked until you subscribe. Incoming messages are still saved and will be forwarded once the subscription is active.
Inactive channels — A channel goes inactive if deleted via API, if the token can't be refreshed, or if the user revokes Meta permissions. Reconnect via a new OAuth flow.
WhatsApp 24-hour window — You can send regular text messages only within 24 hours of the customer's last message. Outside this window, use approved templates via /api/v1/messages/send-template. This is a Meta policy.
Instagram & Facebook 24-hour window — You can reply only within 24 hours of the user's last message. No template workaround — wait for the user to message again.
Text-only messaging — Fiwano supports sending text messages only. For media via WhatsApp, use templates with media headers. Incoming non-text messages are delivered with type: "unsupported".
Fiwano API Documentation