FusionWA/Documentation

Documentation

Quickstart

FusionWA provides WhatsApp Business Cloud API infrastructure for SaaS companies and software houses. Your customers connect their own WhatsApp Business accounts through Meta Embedded Signup — you get the API and webhooks.

Step 1 — Create an account and application

Register at fusionwa.com/register. Then go to Applications and create your first App. Copy the API Key and API Secret.

Step 2 — Embed the connection widget

From your backend, request a short-lived widget token for the customer who is about to see the widget (server-to-server, with your API Secret — never in the browser):

bash
curl -X POST https://fusionwa.com/api/v1/widget/token \
  -H "x-fusionwa-api-key: fwa_live_..." \
  -H "x-fusionwa-api-secret: <your-secret>" \
  -H "Content-Type: application/json" \
  -d '{ "customerId": "user_123" }'

# Response: { "token": "eyJ...", "expiresAt": "2026-10-25T18:00:00.000Z" }

Pass that token to the widget in your frontend. The token is bound to this one customer and expires in 30 minutes — request a fresh one each time the widget is rendered.

html
<script src="https://fusionwa.com/sdk/v1.js"></script>
<div id="fusionwa-widget"></div>
<script>
  FusionWA.init({
    token: "eyJ...",              // from POST /api/v1/widget/token, generated on your backend
    containerId: "fusionwa-widget",
  });
</script>

The SDK renders a secure iframe hosted by fusionwa.com, so you do not need to register your own domain with Meta. If your site uses a Content Security Policy, allow https://fusionwa.com in both script-src andframe-src.

Never generate the widget token in the browser or pass your API Secret to the frontend. The token itself is safe to expose client-side (it is short-lived and scoped to one customer) — your API Secret is not.

Step 3 — Customer connects through Meta Embedded Signup

Your customer clicks "Connect WhatsApp". Meta Embedded Signup opens in a popup. They authorize their WhatsApp Business account. FusionWA receives and stores the connection securely.

For eligible WhatsApp Business App numbers, Meta may offer the Coexistence flow — see the Coexistence section for details.

Step 4 — Configure your webhook

In the dashboard under Applications, set a webhook URL. FusionWA will forward every incoming message and status update to your endpoint in real time.

Step 5 — Send your first message

bash
curl -X POST https://fusionwa.com/api/v1/messages/send \
  -H "X-FusionWA-API-Key: fwa_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+39 02 1234567",
    "templateName": "hello_world",
    "languageCode": "it"
  }'

# Response: { "messageId": "wamid.abc123..." }

SDK Widget

The fastest way to integrate FusionWA. Paste the snippet into your software: the widget manages the full Meta Embedded Signup flow and WhatsApp Business connection on behalf of your customer.

Initialization

javascript
FusionWA.init({
  token: "eyJ...",                // from POST /api/v1/widget/token (server-to-server, your backend)
  containerId: "fusionwa-widget", // id of the div to render into
});

Widget states

NOT_SUBSCRIBEDCustomer not yet activated. Shows "Activate" button.
SUBSCRIBED_UNCONNECTEDActivated but not yet connected. Shows "Connect WhatsApp" button.
CONNECTEDWhatsApp Business connected. Shows number and status.

customerId and the widget token

Use your internal customer identifier — any unique string per App — when requesting a token from POST /api/v1/widget/token. AppUsers are created automatically on first widget activation. The same customerId is used in server-to-server API calls (with your API Secret) to reference that customer's connection.

The widget token is safe to expose client-side: it is short-lived (30 minutes) and bound to one specific customer — unlike the raw customerId, it cannot be reused to read or connect a different customer's number. Generate it on your backend with your API Secret, never in the browser.

WhatsApp Business App Coexistence

Coexistence is a Meta feature that allows a WhatsApp Business App number to be connected to the WhatsApp Business Cloud API simultaneously. When active, the customer continues receiving messages on their phone while the Cloud API (FusionWA) can also send and receive on the same number.

Who it applies to

Coexistence applies to eligible customers who already have an active WhatsApp Business App installation on a mobile device. Not every account is eligible — Meta determines eligibility during the Embedded Signup flow.

The Coexistence flow

When a customer connects via FusionWA and Meta offers Coexistence:

  1. Customer clicks "Connect WhatsApp" in your software.
  2. Meta Embedded Signup popup opens.
  3. Customer selects their existing WhatsApp Business account.
  4. Meta may prompt the customer to scan a QR code on their phone (this is Meta's process, not FusionWA's).
  5. Customer scans the QR in their WhatsApp Business App when requested.
  6. Connection completes — the number is now live on both the phone app and the Cloud API.
The QR code appears only during Meta's Coexistence onboarding, when Meta requests it. It is part of Meta Embedded Signup — FusionWA does not generate QR codes independently. Not all connections use Coexistence; some accounts connect via the standard Cloud API path without a QR scan.

After connecting with Coexistence

The customer continues using WhatsApp Business on their phone normally — sending and receiving from the app as before. The Cloud API (your software via FusionWA) can also send messages and receives all incoming messages via webhook simultaneously.

Coexistence heartbeat

FusionWA automatically manages the Coexistence heartbeat with Meta. No additional configuration is required on your end.

javascript
// No extra configuration needed.
// FusionWA automatically manages the Coexistence
// heartbeat with Meta.
//
// The number stays active on both the mobile app
// and the Cloud API simultaneously.

REST API

Full machine-readable spec: /openapi.yaml (OpenAPI 3.1 — import into Postman or Insomnia).

Widget endpoints (called from the browser, inside the SDK) take a signed token — no API Key or Secret in the browser. Server-to-server endpoints require both x-fusionwa-api-key and x-fusionwa-api-secret.

Create a widget token

Call this from your backend before rendering the widget for a given customer — see SDK Widget.

bash
POST /api/v1/widget/token
x-fusionwa-api-key: fwa_live_...
x-fusionwa-api-secret: <your-secret>

{ "customerId": "user_123" }

# Response
{ "token": "eyJ...", "expiresAt": "2026-10-25T18:00:00.000Z" }

Send a template message

bash
POST /api/v1/messages/send
x-fusionwa-api-key: fwa_live_...
x-fusionwa-api-secret: <your-secret>

{
  "externalCustomerId": "user_123",
  "toPhoneNumber": "+393331234567",
  "template": {
    "name": "booking_confirmation",
    "language": "it",
    "bodyParams": ["Mario Rossi", "15 set 10:00"]
  }
}

# Response
{ "status": "success", "messageId": "wamid.abc123..." }

Send a free-form text message

bash
POST /api/v1/messages/send
x-fusionwa-api-key: fwa_live_...
x-fusionwa-api-secret: <your-secret>

{
  "externalCustomerId": "user_123",
  "toPhoneNumber": "+393331234567",
  "message": "La tua prenotazione è confermata."
}

# Only valid within a 24-hour customer-initiated window

Check connection status

This is what the SDK widget calls internally — you generally do not need to call it yourself. It takes the same token used for FusionWA.init().

bash
GET /api/v1/widget/status?token=eyJ...

# Response — one of:
{ "status": "NOT_SUBSCRIBED" }
{ "status": "SUBSCRIBED_UNCONNECTED", "facebookAppId": "...", "facebookConfigId": "..." }
{ "status": "CONNECTED", "phoneNumber": "+39 333 123 4567", "wabaId": "...", "phoneNumberId": "..." }

List approved templates

bash
GET /api/v1/templates?externalCustomerId=user_123
x-fusionwa-api-key: fwa_live_...
x-fusionwa-api-secret: <your-secret>

# Response
{
  "status": "success",
  "templates": [
    { "name": "booking_confirmation", "status": "APPROVED", "language": "it", "category": "UTILITY", "variableCount": 2 }
  ]
}

Download received images

Call GET /api/v1/media/{mediaId}?externalCustomerId=YOUR_CUSTOMER_ID from your backend with both x-fusionwa-api-key and x-fusionwa-api-secret. Store image.id from incoming, echo or history payloads when available; the message ID is not a media ID.

The endpoint returns JPEG, PNG or WebP bytes (maximum 5 MiB), after checking the application, customer and connected phone. Serve these bytes through your own authenticated, tenant-scoped endpoint. Never expose the secret in frontend code. Responses are private and not cached.

Media can expire or become unavailable; this endpoint is not a permanent archive and cannot recover old messages whose media ID was not saved. Limit: 120 requests per minute per application. See the OpenAPI specification for error responses.

Webhooks

FusionWA forwards every WhatsApp event to your endpoint in real time. Configure the URL in Applications → Webhook in the dashboard.

Zero storage: payloads are forwarded only, never stored.

Payload structure

json
{
  "type": "message",
  "from": "+39 02 1234567",
  "customerId": "user_123",
  "timestamp": "2025-01-15T10:30:00Z",
  "message": {
    "id": "wamid.abc...",
    "type": "text",
    "text": { "body": "Hello" }
  }
}

Event types

messageIncoming message from a customer
statusMessage delivery status update (sent / delivered / read)
template_statusMeta template approval status change

Receive events in your backend

javascript
// Express
app.post("/webhook/whatsapp", express.json(), (req, res) => {
  const { type, from, customerId, message } = req.body;

  if (type === "message" && message?.type === "text") {
    console.log(`[${customerId}] ${from}: ${message.text.body}`);
    // store, process, reply...
  }

  res.sendStatus(200); // respond 200 within 10s or the webhook is retried
});
Failed deliveries are retried with exponential backoff. Permanently failed events are moved to a dead-letter queue.

WhatsApp Templates

Templates enable proactive outbound messages (notifications, reminders, confirmations). They must be approved by Meta before use.

Create and submit a template

bash
POST /api/v1/templates

{
  "customerId": "user_123",
  "name": "order_confirmed",
  "category": "UTILITY",
  "language": "it",
  "components": [
    {
      "type": "BODY",
      "text": "Your order {{1}} is confirmed. Delivery: {{2}}."
    }
  ]
}

The template is submitted to Meta for review. Status becomes APPROVED typically within a few hours. You receive a template_status webhook event when the status changes.

Error Reference

200Success
400Invalid request body
401Missing or invalid API Key
403App revoked or customerId not authorized
404Customer not found or not connected
422Validation error (see response body)
429Rate limit exceeded
500Internal error — retry with backoff

For AI Coding Assistants

Use this block as initial context in your prompt when integrating FusionWA with Claude Code, Codex, Cursor, or GitHub Copilot:

markdown
# FusionWA Integration Context

Read https://fusionwa.com/llms.txt for the full machine-readable specification.

## SDK Widget
# 1. Server-to-server, with your API Secret, before rendering the widget:
POST /api/v1/widget/token  { customerId }  ->  { token, expiresAt }
# 2. In the browser, with that token:
<script src="https://fusionwa.com/sdk/v1.js"></script>
<div id="fusionwa-widget"></div>
<script>
  FusionWA.init({ token, containerId })
</script>

## REST API
Base URL: https://fusionwa.com/api/v1
Auth: X-FusionWA-API-Key: fwa_live_... (+ X-FusionWA-API-Secret for server-to-server calls)

POST /api/v1/widget/token
  body: { customerId }
  returns: { token, expiresAt }

POST /api/v1/messages/send
  body: { to, templateName, languageCode, components[] }
  returns: { messageId }

GET /api/v1/widget/status?token=
  returns: { status: "INACTIVE"|"CONNECTED", phoneNumber }

GET /api/v1/templates?customerId=
  returns: [{ name, status, language, category }]

## Webhooks
Configure URL in dashboard. Payload: { type, from, customerId, timestamp, message }
Respond 200 within 10s. type: "message" | "status" | "template_status"

## Security rules
- token (from POST /api/v1/widget/token): client-safe, but short-lived and scoped to one customer — generate it on your backend
- apiKey: client-safe only as part of server-to-server calls (with the secret); never alone in the browser
- API Secret: server-only (never expose)
- customerId: your internal ID, any unique string — only trusted when it comes from a server-to-server call (apiKey+secret) or from a verified widget token, never a raw value sent by the browser
- Do NOT store messages — forward to your own DB if needed
- Do NOT invent endpoints — use only the documented ones above

Download the machine-readable version: /llms.txt