The full API contract is maintained in English. Download this documentation as Markdown for AI-agent 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 text | ✅ | ✅ | ✅ |
| Outbound text — max length | 4096 chars | 1000 chars | 2000 chars |
| Outbound media (Pro) | image, audio, video, document | image, video, audio | image, audio, video, file |
| Template messages (Pro) | ✅ Required outside 24h window | ❌ Not supported | ❌ Not supported |
| Incoming webhooks — text | ✅ type: "text" |
✅ type: "text" |
✅ type: "text" |
| Incoming webhooks — media (Pro) | image, audio, video, document, sticker | image, audio, video, file | image, audio, video, file |
| Incoming webhooks — media (Starter) | type: "unsupported" + upgrade_required: "pro" |
Same | Same |
| 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.
Fiwano offers two license tiers. Each connected channel requires an active license.
| Tier | Monthly | Capabilities |
|---|---|---|
| Starter | $12 | Unlimited inbound and outbound text messages, delivery statuses |
| Pro | $19 | Everything in Starter + inbound media with files, outbound media via HTTPS URL (signed URLs supported), WhatsApp template management and sending |
New accounts start with a 7-day free trial (Pro tier). Upgrade or manage subscriptions via the Billing page in the portal.
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 https://fiwano.com/api/v1/channels \
-H "X-API-Key: YOUR_API_KEY"
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:
curl -X POST https://fiwano.com/api/v1/redirects \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"uri_pattern": "https://yourapp.com/callback"}'
Step 2. Request a setup URL (valid for 10 minutes):
curl -X POST https://fiwano.com/api/v1/channels/setup-url \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"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):
curl -X POST https://fiwano.com/api/v1/channels/exchange-code \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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).
curl https://fiwano.com/api/v1/channels \
-H "X-API-Key: YOUR_API_KEY"
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",
"subscription": {
"status": "active",
"source": "paddle",
"tier": "pro",
"expires_at": "2025-02-15T10:30:00",
"auto_renew": true
}
}
],
"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) |
subscription |
Subscription/license state object — see below |
subscription objectThe current billing state of the channel. Always present — for a channel that is not bound to any active subscription, status is "none" and the rest are null/false.
| Field | Type | Description |
|---|---|---|
status |
string | active — channel can send/receive. expired — billing period ended (or scheduled cancellation reached expires_at). canceled — subscription was canceled mid-period. none — no subscription bound; channel cannot send/receive messages. |
source |
string | null | Where the entitlement came from: trial (auto-granted on signup), paddle (paid subscription via Paddle), enterprise (custom subscription provisioned by Fiwano staff, e.g. partner deal or invoice billing). null when status is none. |
tier |
string | null | starter or pro. pro is required for media messages and WhatsApp template CRUD/send. null when status is none. |
expires_at |
string | null | ISO-8601 UTC timestamp when the current billing period ends. null only when status is none. If auto_renew is true, this is the next renewal date; otherwise this is the cutoff after which the channel will stop working. |
auto_renew |
boolean | true only for an active Paddle subscription that will renew on expires_at. Becomes false as soon as the customer cancels in Paddle — the subscription stays usable until expires_at, then lapses. Always false for trial and Enterprise licenses (Enterprise renewals are handled out-of-band by Fiwano staff before expires_at). |
Common combinations:
{status: "active", source: "paddle", auto_renew: true, expires_at: <next renewal>}{status: "active", source: "paddle", auto_renew: false, expires_at: <cutoff>}. Service continues until expires_at, then channel becomes orphaned.{status: "active", source: "trial", tier: "pro", auto_renew: false, expires_at: <signup + 7 days>}{status: "active", source: "enterprise", auto_renew: false, expires_at: <agreed term end>}. Renewals are arranged with Fiwano staff before expires_at.{status: "none", source: null, tier: null, expires_at: null, auto_renew: false}. Send/receive will fail; reconnect a license to restore service.Get details of a specific channel. Same response format (including the subscription block) as the list endpoint.
curl https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890 \
-H "X-API-Key: YOUR_API_KEY"
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 |
curl -X POST https://fiwano.com/api/v1/channels/setup-url \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel_type": "whatsapp", "redirect_uri": "https://yourapp.com/callback"}'
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:
curl -X PATCH https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"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. Fiwano also attempts to unsubscribe the channel's Meta webhook resource when it is safe: WABA subscriptions are kept if another active WhatsApp channel uses the same WABA, and Page subscriptions are kept if another active Instagram/Facebook channel uses the same Page. You can reconnect the same Meta account later via a new OAuth flow.
curl -X DELETE https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890 \
-H "X-API-Key: YOUR_API_KEY"
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. Max length is platform-dependent: WhatsApp 4096, Facebook Messenger 2000, Instagram 1000. Longer messages are rejected by Meta — split on your side. |
WhatsApp example:
curl -X POST https://fiwano.com/api/v1/messages/send \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel_id": "a1b2c3d4e5f67890", "recipient": "1234567890", "text": "Hello! Your order is ready."}'
Instagram example:
curl -X POST https://fiwano.com/api/v1/messages/send \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"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.
Each platform enforces a hard maximum on outgoing text length. Fiwano validates the length before calling Meta and rejects oversize requests immediately:
| Platform | Max text length |
|---|---|
| 4096 characters | |
| Facebook Messenger | 2000 characters |
| 1000 characters |
If text exceeds the limit, the API returns 400 Bad Request with a structured error:
{
"detail": {
"code": "text_too_long",
"message": "Text exceeds instagram limit of 1000 characters (got 1234).",
"actual_length": 1234,
"max_length": 1000,
"channel_type": "instagram",
"hint": "Split the text on your side and send as multiple messages."
}
}
Fiwano does not auto-split long messages — splitting is the caller's responsibility (preserves your chunking conventions, ordering and headers).
| Failure type | Examples | What Fiwano does |
|---|---|---|
| Transient (network, 5xx, rate limit) | Meta 500/503, timeouts | Response: 200 OK with status: "queued". Background retries up to 7 times over ~20 minutes. After 3 failed retries — early-warning email; after final expiry — failure email + Telegram alert. |
| Permanent (Meta error code 100) | Text too long, invalid recipient, malformed payload, bad parameter | Response: 200 OK with success: false, status: "failed", error: "...". Not enqueued for retry. Immediate alert to admin and email to channel owner — fix the request and resend. |
If a message somehow reaches the retry queue and Meta later returns a permanent error, Fiwano aborts further retries on that entry and notifies the user immediately.
WhatsApp only. Pro required. 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:
curl -X POST https://fiwano.com/api/v1/messages/send-template \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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:
curl -X POST https://fiwano.com/api/v1/messages/send-template \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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.
Pro required. Meta fetches the file directly from media_url — Fiwano does not download or store it.
| Field | Type | Required | Description |
|---|---|---|---|
channel_id |
string | Yes | Channel to send from |
recipient |
string | Yes | Recipient identifier (see channel capabilities) |
media_type |
string | Yes | image, audio, video, or document |
media_url |
string | Yes | HTTPS URL, max 2048 chars. No credentials in URL, no private IPs. |
caption |
string | No | Caption, max 1024 chars (WhatsApp image/video/document) |
filename |
string | No | Filename, max 255 chars (WhatsApp document only) |
Use a signed URL for non-public content — S3/GCS/R2 presigned, Azure SAS, or HMAC-signed URL on your own server. Set expiry ≥ 5 min. Public URLs are accessible to anyone who learns them.
curl -X POST https://fiwano.com/api/v1/messages/send-media \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{
"channel_id": "a1b2c3d4e5f67890",
"recipient": "1234567890",
"media_type": "image",
"media_url": "https://my-bucket.s3.amazonaws.com/photo.jpg?X-Amz-Signature=...&X-Amz-Expires=600",
"caption": "Your order photo"
}'
Always check success. On failure, error_code (when present) identifies the Meta error:
{ "success": false, "error_code": 131052, "error": "...Hint: ...", "status": "failed" }
error_code |
Meaning | What to do |
|---|---|---|
100 |
Invalid parameter | Check media_url, recipient, media_type. |
131052 |
Meta could not download from URL | Verify URL is reachable, returns 200, correct Content-Type, signed URL not expired. |
131053 |
Upload error | Unsupported format/size, or Meta rate-limited your hosting provider's network. Try AWS S3, GCS, or Cloudflare R2 instead. |
131026 |
Recipient invalid | Recipient is not a valid WhatsApp user, or messaging restricted. |
131047, 131057 |
Outside 24h window (WhatsApp) | Use an approved template. |
190, 200, 10 |
Token issue | Reconnect the channel. |
Size limits (per Meta): WhatsApp — image 5 MB, audio/video 16 MB, document 100 MB. Instagram & Messenger — 8–25 MB depending on type. On exceeding: error_code: 131053.
| HTTP Status | Meaning |
|---|---|
200 |
Request accepted (check success) |
402 |
Pro license required |
404 |
Channel not found |
422 |
Validation failed (URL/media_type/filename) |
Media files received from inbound webhooks are stored temporarily. Download them via the endpoint below. Files expire after 60 minutes — after expiry, the URL returns 410 Gone.
Download a media file. The media_id comes from the data.media.media_id field in inbound webhook payloads (see Incoming Webhooks — media).
curl https://fiwano.com/api/v1/media/a1b2c3d4e5f67890 \
-H "X-API-Key: YOUR_API_KEY" \
--output photo.jpg
Response is a raw binary file with appropriate Content-Type header (e.g. image/jpeg, audio/ogg, video/mp4). If the original filename is known, it's included in Content-Disposition.
| HTTP Status | Meaning |
|---|---|
200 |
File returned |
404 |
File not found (wrong ID or belongs to another account) |
410 |
File expired or unavailable (already cleaned up, or download from Meta had failed) |
Tip: Download media files as soon as possible after receiving the webhook. Files are cleaned up after 60 minutes. If you need to keep them longer, save them in your own storage.
WhatsApp requires pre-approved message templates to start conversations outside the 24-hour customer service window. Template list, sync, create, update, delete, portal test sends, and API send-template require a Pro license. 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 |
curl https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates \
-H "X-API-Key: YOUR_API_KEY"
Get template details including components and variable definitions.
curl https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates/tpl_abc123 \
-H "X-API-Key: YOUR_API_KEY"
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:
curl -X POST https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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) |
curl -X PUT https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates/tpl_abc123 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} has been updated.",
"example": {"body_text": [["Pablo", "ORD-123"]]}
}
]
}'
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.
curl -X DELETE https://fiwano.com/api/v1/channels/a1b2c3d4e5f67890/templates/tpl_abc123 \
-H "X-API-Key: YOUR_API_KEY"
| 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).
With a Pro license, media messages include the file content. The media file is downloaded from Meta and stored temporarily. Use the download_url to fetch the file before it expires.
WhatsApp image example:
{
"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": "image",
"caption": "Check this photo",
"media": {
"media_id": "m1b2c3d4e5f67890",
"kind": "image",
"mime_type": "image/jpeg",
"file_size": 245760,
"filename": null,
"sha256": "abc123...",
"duration_ms": null,
"download_url": "https://fiwano.com/api/v1/media/m1b2c3d4e5f67890",
"expires_at": "2025-01-15T11:30:00Z"
}
}
}
WhatsApp voice message example:
{
"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": "audio",
"media": {
"media_id": "m2b3c4d5e6f78901",
"kind": "voice",
"mime_type": "audio/ogg; codecs=opus",
"file_size": 12345,
"filename": null,
"sha256": "def456...",
"duration_ms": 5200,
"download_url": "https://fiwano.com/api/v1/media/m2b3c4d5e6f78901",
"expires_at": "2025-01-15T11:30:00Z"
}
}
}
Media payload fields:
| Field | Type | Description |
|---|---|---|
media_id |
string | Media file ID — use in GET /api/v1/media/{media_id} to download |
kind |
string | image, audio, voice, video, document, sticker |
mime_type |
string | MIME type (e.g. image/jpeg, audio/ogg; codecs=opus) |
file_size |
int | File size in bytes |
filename |
string|null | Original filename (documents only) |
sha256 |
string|null | SHA-256 hash from Meta (WhatsApp only) |
duration_ms |
int|null | Duration in milliseconds (audio/video only) |
download_url |
string|null | Authenticated download URL. null if download from Meta failed. |
error |
string | Present only when download failed — describes the error |
expires_at |
string | ISO 8601 timestamp — file is deleted after this time |
Note:
kinddistinguishes voice messages from regular audio files. WhatsApp sends voice messages astype: "audio"with avoiceflag — Fiwano normalizes this tokind: "voice".
With a Starter license, media messages are delivered with type: "unsupported" and an upgrade_required field hinting which license is needed:
{
"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",
"upgrade_required": "pro"
}
}
The upgrade_required field tells you which license tier is needed. Upgrade via the Billing page in the portal to receive full media content.
Note: Messages with truly unsupported types (e.g.
location,contacts,reaction) still arrive astype: "unsupported"withoutupgrade_required, regardless of your license tier.
{
"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 https://fiwano.com/api/v1/channels/b2c3d4e5f6789012/profile/6543217890123456 \
-H "X-API-Key: YOUR_API_KEY"
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.
curl https://fiwano.com/api/v1/redirects \
-H "X-API-Key: YOUR_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) |
curl -X POST https://fiwano.com/api/v1/redirects \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"uri_pattern": "https://yourapp.com/callback"}'
Remove an allowed redirect URI.
curl -X DELETE https://fiwano.com/api/v1/redirects/redir_abc123 \
-H "X-API-Key: YOUR_API_KEY"
| 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).
Fiwano is a verified n8n community node — listed on n8n.io/integrations/fiwano/. Use it to build WhatsApp, Instagram and Facebook Messenger automations, AI agent workflows, and chatbots.
On n8n Cloud, installation may need to be enabled by the instance owner in the Cloud Admin Panel first.
Use only when in-app installation is unavailable in your environment (e.g. restricted self-hosted setup).
mkdir -p ~/.n8n/nodes && cd ~/.n8n/nodes
npm install n8n-nodes-fiwano
# Restart n8n
For self-hosted Docker: build this package into a custom n8n image — see the GitHub repository for details.
| 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), Send Media (image/audio/video/document) |
| 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.
Media files — Inbound media files (images, audio, video, documents) are stored temporarily for 60 minutes. Download them via GET /api/v1/media/{media_id} as soon as possible after receiving the webhook. Files are automatically cleaned up after expiry. Maximum file size: 10 MB.
Pro license — Sending and receiving media and using WhatsApp templates requires a Pro license ($19/mo). With a Starter license ($12/mo), media messages arrive as type: "unsupported" with upgrade_required: "pro". Upgrade via the Billing page.
Fiwano API Documentation