# 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`](https://www.npmjs.com/package/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 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](#sender-profile) | Via [profile endpoint](#sender-profile) |

> **Note:** Each Meta account (phone number, Instagram account, or Facebook Page) can only be connected to one Fiwano user at a time.

---

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

---

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

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

---

## Channels

### GET /api/v1/channels

List all channels for your account (both active and inactive).

Response:

```json
{
  "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 /api/v1/channels/{channel_id}

Get details of a specific channel. Same response format as the list endpoint.

### POST /api/v1/channels/setup-url

Generate an OAuth URL to connect a new channel. See [API connection flow](#option-b-via-api-programmatic) 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:

```json
{
  "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](#event-types) for available values per channel type. If omitted, no events are delivered until configured. |

WhatsApp response:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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. You can reconnect the same Meta account later via a new OAuth flow.

---

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

WhatsApp example:

```json
{
  "channel_id": "a1b2c3d4e5f67890",
  "recipient": "1234567890",
  "text": "Hello! Your order is ready."
}
```

Instagram example:

```json
{
  "channel_id": "b2c3d4e5f6789012",
  "recipient": "6543217890123456",
  "text": "Thanks for reaching out!"
}
```

Response:

```json
{
  "success": true,
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

The `message_id` (UUID) is used in all subsequent delivery status webhooks.

### POST /api/v1/messages/send-template

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

```json
{
  "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:

```json
{
  "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 Templates

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

### 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` |

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

Get template details including components and variable definitions.

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

```json
{
  "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) |

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

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

```python
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.

- `message_id` is always present in all status events — it's a UUID generated by Fiwano, not a Meta internal ID.
- Status progression: `sent → delivered → read`. Each status implies all previous ones.
- `data.recipient` is the user identifier: phone number (WhatsApp), IGSID (Instagram), or PSID (Facebook).
- All channels use the exact same webhook format.
- **Read cascading:** when a user reads a conversation, Fiwano sends a separate `message.read` webhook for *each* unread message — not just the latest one.

### Payload Format

All payloads share the same top-level structure:

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

#### message.received (WhatsApp)

```json
{
  "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)

```json
{
  "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)

```json
{
  "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 — unsupported type (all channels)

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

#### message.delivered / message.read (all channels)

```json
{
  "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)

```json
{
  "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:

- **7 retry attempts** with exponential backoff: 30s, 1m, 2m, 2m, 2m, 2m, 2m (~12 minutes total)
- **20-minute hard deadline** — after which the delivery is marked as permanently failed
- **Email warning** sent after the 3rd failed attempt (retries still in progress)
- **Email alert** sent when all retries are exhausted (permanent failure)
- Payloads are encrypted at rest during retry and cleared after delivery or expiry

**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 endpoint](#sender-profile) — `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.

---

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

- **WhatsApp:** Not supported — sender name is included in every webhook payload (`data.from_name`).
- **Instagram:** Returns `username`, `name`, `profile_pic`, `follower_count`, `is_verified`.
- **Facebook:** Returns `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.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 -H "X-API-Key: mip_live_abc123..." \
     https://fiwano.com/api/v1/channels/b2c3d4e5f6789012/profile/6543217890123456
```

Response (Instagram):

```json
{
  "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):

```json
{
  "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):

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

### 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`) |

### DELETE /api/v1/redirects/{redirect_id}

Remove an allowed redirect URI.

---

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

```json
{ "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

Official Fiwano community nodes for [n8n](https://n8n.io) are available on npm: [`n8n-nodes-fiwano`](https://www.npmjs.com/package/n8n-nodes-fiwano).

Use them to build WhatsApp, Instagram and Facebook Messenger automations, AI agent workflows, and chatbots without writing custom API integration code.

### Install

```bash
npm install n8n-nodes-fiwano
```

Or from n8n UI: **Settings → Community Nodes → Install** → enter `n8n-nodes-fiwano`.

### 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) |
| 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](https://github.com/RomanBabakin/n8n-nodes-fiwano) 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.

**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"`.
