Get in Touch

Have a question about the platform, need help with your integration, or want to discuss partnership opportunities and enterprise pricing? Drop us an email — we’ll do our best to get back to you within 3–4 hours.

contact@fiwano.com

The full API contract is maintained in English. Download this documentation as Markdown for AI-agent context.

Download .md

Contents

Fiwano — API Documentation

Unified 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

Channel Capabilities

All three channels are connected the same way (OAuth). The table below shows what each channel supports.

Feature WhatsApp Instagram 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.


License Tiers

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.


Authentication

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.


Connecting Channels

Option A: Via Portal (Self-service)

  1. Go to Channels → Connect Channel in the portal.
  2. Select the channel type (WhatsApp, Instagram, or Facebook Messenger).
  3. Complete the Meta OAuth flow in the popup window.
  4. Configure the Webhook URL and select Webhook Events in channel settings.
  5. By default, no events are enabled — select which events to forward to your endpoint.

A webhook_secret is auto-generated when you first set a webhook URL. Use it to verify incoming webhook signatures.

Option B: Via API (Programmatic)

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.


Channels

GET /api/v1/channels

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 object

The 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:

GET /api/v1/channels/{channel_id}

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"

POST /api/v1/channels/setup-url

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.

POST /api/v1/channels/exchange-code

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"]
}

PATCH /api/v1/channels/{channel_id}

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.

DELETE /api/v1/channels/{channel_id}

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"

Sending Messages

POST /api/v1/messages/send

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.

Text length limits & errors

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
WhatsApp 4096 characters
Facebook Messenger 2000 characters
Instagram 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).

Behavior on transient vs permanent Meta errors

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.

POST /api/v1/messages/send-template

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.

POST /api/v1/messages/send-media

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

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.

GET /api/v1/media/{media_id}

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 Templates

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).

GET /api/v1/channels/{channel_id}/templates

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 /api/v1/channels/{channel_id}/templates/{template_id}

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"

POST /api/v1/channels/{channel_id}/templates

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.

PUT /api/v1/channels/{channel_id}/templates/{template_id}

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 PENDING for re-review.

DELETE /api/v1/channels/{channel_id}/templates/{template_id}

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 APPROVED template, you cannot create a new template with the same name for 30 days (Meta restriction).


Incoming Webhooks

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.

Verifying Signatures

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)

Event Types

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 WhatsApp
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 WhatsApp

* 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
WhatsApp message.received, message.sent, message.delivered, message.read, message.failed
Instagram message.received, message.delivered, message.read
Facebook message.received, message.delivered, message.read

Delivery Status Tracking

When you send a message via /api/v1/messages/send, you receive a message_id (UUID). All subsequent status webhooks reference this same UUID.

Payload Format

All payloads share the same top-level structure:

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": { ... }
}

message.received (WhatsApp)

{
  "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.

message.received (Instagram)

{
  "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).

message.received (Facebook Messenger)

{
  "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).

message.received — media (Pro)

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: kind distinguishes voice messages from regular audio files. WhatsApp sends voice messages as type: "audio" with a voice flag — Fiwano normalizes this to kind: "voice".

message.received — unsupported type (all channels)

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 as type: "unsupported" without upgrade_required, regardless of your license tier.

message.delivered / message.read (all channels)

{
  "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.

message.failed (WhatsApp)

{
  "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"}]
  }
}

Retry Policy

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 Profile

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 endpointGET /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.


Sender Profile

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.

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.from value, then cache the result on your side. No need to call it on every message.

GET /api/v1/channels/{channel_id}/profile/{user_id}

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)

Redirect URIs

Manage allowed redirect URIs for your API key. Required for the programmatic channel connection flow.

GET /api/v1/redirects

List allowed redirect URIs for the current API key.

curl https://fiwano.com/api/v1/redirects \
  -H "X-API-Key: YOUR_API_KEY"

POST /api/v1/redirects

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"}'

DELETE /api/v1/redirects/{redirect_id}

Remove an allowed redirect URI.

curl -X DELETE https://fiwano.com/api/v1/redirects/redir_abc123 \
  -H "X-API-Key: YOUR_API_KEY"

Errors & Rate Limits

HTTP Status Codes

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.

Error Format

{ "detail": "Human-readable error description" }

Rate Limits

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).


n8n Integration

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.

Install

  1. Open the nodes panel with + or N
  2. Search for Fiwano
  3. Select Fiwano under More from the community
  4. Click Install

On n8n Cloud, installation may need to be enabled by the instance owner in the Cloud Admin Panel first.

Manual fallback (npm)

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.

Nodes

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

Action node — operations

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

Trigger node — events

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 WhatsApp
message.failed WhatsApp

HMAC-SHA256 signature verification is built in — the trigger validates every webhook automatically.

Example workflow

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.


Important Notes

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