Back to home

Public reference

No login required to read this page. Integrations use workspace API keys from the dashboard (Sign in → Settings → API keys).

Public partner API (API keys)

Server-to-server integration for sending WhatsApp messages from your own backend without a user login. Authentication uses a workspace API key and secret (apiKey / apiSecret headers).


1. Creating API keys

  1. Sign in to the Flowziac dashboard.
  2. Open Settings (workspace settings).
  3. Find the API keys section (visible to workspace owners and admins only).
  4. Optionally enter a label, then click Generate key.
  5. Copy apiKey and apiSecret immediately. The secret is shown only once; store it in a secure environment variable on your server.

Revoked keys stop working at once. You can create multiple keys per workspace (e.g. staging vs production).


2. Base URL

Use this deployment’s partner API base (updates automatically on this page):

API base URLhttps://flowziac.com/api/v1/public

3. Authentication

Send these headers on every request:

HeaderAlternativeValue
apiKeyX-API-KeyPublic key id (starts with pk_…)
apiSecretX-API-SecretSecret shown once at creation

Example:

apiKey: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
apiSecret: <your-secret>
Content-Type: application/json

Invalid or revoked credentials return 401 with a JSON error body.


4. Endpoints

4.1 Send a message

POST /api/v1/public/messages/send

Recipient — provide either:

  • phone: WhatsApp-ready number (digits, country code, no + required in JSON), or
  • contactId: an existing contact id in that workspace.

Common optional fields

FieldDescription
phoneNumberIdInternal Flowziac phone record id (phone.id). Required when the workspace has more than one active WhatsApp number; otherwise optional (the active / default line is used).
idempotencyKeyOptional string (max 255). Safe retries: same key + same body returns the original message (HTTP 200, idempotentReplay: true). Same key + different body → 409 IDEMPOTENCY_CONFLICT. You can also send header Idempotency-Key / idempotency-key instead of (or in addition to) the body field.
revalidateFlowSessionOptional boolean. When true and you send with contactId, closes any active flow session for that contact (optionally scoped to the same line) before sending.
bodyParametersOptional map of main-body slots ("1", "2", …). Applied after templateVariables (useful with carousel payloads).

Where to get phoneNumberId

  • Dashboard → Phone numbers → open the line → copy the id from the URL: /dashboard/phone-numbers/<THIS_ID>/templates
  • Or GET /api/v1/workspaces/{workspaceId}/phone-numbers (JWT session) and use each item’s id

Do not use Meta’s numeric Cloud API phone id (the long digits shown as WhatsApp phone number id). That is a different field and will not work here.

If you omit phoneNumberId with multiple active lines, the API returns PHONE_NUMBER_REQUIRED.

Text message

{
  "type": "text",
  "phone": "919876543210",
  "content": "Hello from our system."
}

Template message (simple — recommended)

Use the approved template name as in the dashboard. Map {{1}}, {{2}}, … to string keys "1", "2", … in templateVariables.

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "order_confirmation",
  "templateLanguage": "en",
  "templateVariables": {
    "1": "Jane",
    "2": "ORD-9921",
    "3": "Tomorrow 5pm"
  }
}
  • templateLanguage is optional; it helps match the stored template if you use locale codes (e.g. en vs en_US). When using templateComponents, the language you send is passed to Meta as-is — it must match the approved template language exactly.
  • The template must be approved in Meta / Flowziac; pending or rejected templates cannot be sent.
  • Prefer templateVariables for most integrations. Use templateComponents only when you need full Meta Cloud API control (see below).
  • For type: "template", you must provide at least one of: templateVariables, templateComponents, bodyParameters, or carousel.cards.

Templates with buttons (URL / copy code)

Button values are not a separate top-level field for normal templates. Put them in the same templateVariables map (or use templateComponents).

Button typeWhat to send
Dynamic URL (https://example.com/…{{1}})Numeric key matching the {{n}} in the stored URL text (often "1" — see Meta rules below)
Copy code"copy_code": "SAVE20"
Catalog / MPM"catalog_product_retailer_id": "..." (optional)
Quick reply / static URL (no {{n}})Nothing — omit button values

Meta rules when creating the template (dashboard / Meta)

These apply when you create the template. Getting them wrong causes Meta errors such as Button URL (at index 0) has invalid format.

  • URL button label and URL are separate fields (e.g. label View Offer, URL https://…).
  • Dynamic URL buttons support exactly one variable, and it must be {{1}} on the button URL — not {{2}} / {{3}} continuing body numbering.
  • The variable must be at the end of the URL string (path or query), e.g. https://watsbuy.com/magic-login?token={{1}}.
  • No leading/trailing spaces; URL must start with https://.
  • Body can still use {{1}}, {{2}}, … independently of the button’s {{1}}.

Example template shape

  • Body: Hello {{1}}, … special *{{2}}*.
  • URL button text: View Offer
  • URL: https://watsbuy.com/magic-login?token={{1}}

Send with templateVariables (recommended when body {{1}} and URL {{1}} can share the same value)

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "broadcast_offer_template",
  "templateLanguage": "en",
  "templateVariables": {
    "1": "Jane",
    "2": "Summer Deal"
  }
}

Flowziac builds Meta’s button component for you. For a dynamic URL, the value is only the suffix that replaces {{1}} (e.g. a token or path segment), not the full https://… URL.

Important: In templateVariables, keys are taken from the literal {{n}} numbers in the stored template. Body {{1}} and URL button {{1}} both map to key "1", so they receive the same string. That is fine when they should match; if the URL token must differ from body {{1}} (e.g. a magic-login token), use templateComponents instead.

Send with templateComponents (body + URL button with different values)

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "broadcast_offer_template",
  "templateLanguage": "en",
  "templateComponents": [
    {
      "type": "body",
      "parameters": [
        { "type": "text", "text": "Jane" },
        { "type": "text", "text": "Summer Deal" }
      ]
    },
    {
      "type": "button",
      "sub_type": "url",
      "index": "0",
      "parameters": [
        { "type": "text", "text": "YOUR_MAGIC_TOKEN" }
      ]
    }
  ]
}
  • index is 0-based button order on the template ("0" = first button).
  • parameters[].text for a URL button is only the dynamic suffix (replaces {{1}} in the template URL).
  • Prefer percent-encoding special characters in URL parameter values (Meta requirement).
  • When templateComponents is provided, Flowziac forwards them to Meta and does not rebuild components from templateVariables.

Copy-code button example

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "promo_with_code",
  "templateLanguage": "en",
  "templateVariables": {
    "1": "Jane",
    "copy_code": "SAVE20"
  }
}

Template message (advanced)

If you already build WhatsApp Cloud API components yourself, pass templateComponents (array) instead of relying on templateVariables. Use this when:

  • Body and dynamic URL button need different values for their respective {{1}} slots, or
  • You need full control over header media / button indexes.

Do not treat templateVariables and templateComponents as two parallel ways to fill the same slots in one request; pick the form that matches your needs (simple map vs full Cloud API components).

Media card carousel templates (marketing)

These templates are created in the Flowziac dashboard (Carousel (marketing) in the template builder). They are not the same as standard single-bubble templates.

  • Simple templateVariables mapping: Main bubble {{1}}, {{2}} map to keys "1", "2" as usual. For each carousel card, placeholders use keys c{cardIndex}_{slot} — e.g. card 0’s {{1}} is c0_1, card 1’s {{1}} is c1_1 (same pattern for URL variables on card buttons). Card header media ids use c{cardIndex}_header.
  • Structured carousel payload (optional): Instead of only flat templateVariables, you can send carousel.cards[] with bodyValues, buttonValues, and header. Body values can use numeric keys ("1", "2", …) matching {{1}}, {{2}}, or named keys; named keys are mapped to body slots in JSON key order (insertion order). Optional bodyParameters overrides main-bubble numeric slots ("1", "2") after templateVariables.

Card header media — provide one of:

FieldDescription
header.linkPublic image/video URL. Flowziac auto-uploads it to WhatsApp before sending. (Recommended)
header.idPre-uploaded WhatsApp media ID (numeric string). Faster if you already have a media ID.

Example with header.link (recommended):

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "your_carousel_template",
  "templateLanguage": "en_US",
  "templateVariables": { "1": "Main intro line" },
  "carousel": {
    "cards": [
      {
        "index": 0,
        "bodyValues": { "1": "Villa A", "2": "Dubai", "3": "1.2M" },
        "buttonValues": [
          {
            "index": 0,
            "sub_type": "url",
            "parameters": { "type": "text", "text": "slug-for-url" }
          }
        ],
        "header": { "format": "image", "link": "https://example.com/villa-a.jpg" }
      },
      {
        "index": 1,
        "bodyValues": { "1": "Villa B", "2": "Abu Dhabi", "3": "2.5M" },
        "header": { "format": "image", "link": "https://example.com/villa-b.jpg" }
      }
    ]
  }
}

Example with header.id (pre-uploaded media ID):

{
  "type": "template",
  "phone": "919876543210",
  "phoneNumberId": "<flowziac-phone-id>",
  "templateName": "your_carousel_template",
  "templateLanguage": "en_US",
  "templateVariables": { "1": "Main intro line" },
  "carousel": {
    "cards": [
      {
        "index": 0,
        "bodyValues": { "1": "Villa A", "2": "Dubai", "3": "1.2M" },
        "header": { "format": "image", "id": "1038027398627028" }
      }
    ]
  }
}
  • If you already build the Cloud API payload, pass templateComponents with body and carousel components per Meta’s send format.

Interactive message

{
  "type": "interactive",
  "phone": "919876543210",
  "content": "{\"type\":\"button\",\"body\":\"Choose\",\"buttons\":[...]}"
}

content must be a JSON string matching your app’s interactive payload format (button, list, cta_url, etc.).

Success response (send)

HTTP 201 on a new send (or HTTP 200 when replaying an idempotent request):

{
  "success": true,
  "data": {
    "id": "cms9xrndw001jbj8etea2h8v1",
    "status": "SENT",
    "whatsappMessageId": "wamid.HBgMOTE5NzQ2MTExMTkzFQIAERgS...",
    "failureReason": null,
    "sentAt": "2026-08-01T05:33:48.451Z",
    "deliveredAt": null,
    "readAt": null,
    "createdAt": "2026-08-01T05:33:48.452Z",
    "idempotentReplay": false
  }
}
FieldMeaning
idFlowziac message id — use with Get message status
statusUppercased DB status (SENT, DELIVERED, READ, FAILED, …)
whatsappMessageIdMeta wamid when Graph accepted the send
deliveredAt / readAtFilled when Meta status webhooks update the message
idempotentReplaytrue if this response is a replay of a prior send with the same idempotency key

Important: status: "SENT" + a whatsappMessageId means Meta accepted the message. It does not guarantee the user has seen it on WhatsApp yet. Delivery/read (and some failures) arrive later via Meta webhooks. Poll Get message status if deliveredAt stays null.


4.2 Get message status

GET /api/v1/public/messages/:messageId

:messageId is the Flowziac id returned from send (not the Meta wamid).

Success (HTTP 200) — same data shape as the send response:

{
  "success": true,
  "data": {
    "id": "cms9xrndw001jbj8etea2h8v1",
    "status": "SENT",
    "whatsappMessageId": "wamid....",
    "failureReason": null,
    "sentAt": "2026-08-01T05:33:48.451Z",
    "deliveredAt": null,
    "readAt": null,
    "createdAt": "2026-08-01T05:33:48.452Z",
    "idempotentReplay": false
  }
}
Status (typical)Meaning
SENTAccepted by Meta; delivery not confirmed in Flowziac yet
DELIVEREDDevice received the message (deliveredAt set)
READUser opened the message (readAt set)
FAILEDDelivery failed after accept (check failureReason / Meta logs)

If status stays SENT with deliveredAt: null, either Meta has not delivered yet, or Meta status webhooks are not updating Flowziac. Confirm webhooks for your WhatsApp number and check Meta WhatsApp Manager / message logs for the wamid.

Not found404 NOT_FOUND.


4.3 List approved templates

GET /api/v1/public/templates

Returns approved templates for the workspace (same workspace as the API key). Use this to discover templateName, language codes, and component structure (including button URLs).

Success (HTTP 200):

{
  "success": true,
  "data": {
    "templates": [ /* array of template records */ ]
  }
}

5. Errors

Error body shape:

{
  "error": "ERROR_CODE",
  "message": "Human-readable explanation.",
  "details": {}
}

(details is present for some errors, e.g. WhatsApp Graph failures.)

HTTPerror codeWhen
401UNAUTHORIZEDMissing/invalid/revoked apiKey / apiSecret
400PHONE_NUMBER_REQUIREDMultiple active lines and no phoneNumberId
400PHONE_NUMBER_NOT_FOUNDphoneNumberId not in this workspace
400PHONE_NUMBER_INACTIVERequested line is inactive
400NO_PHONE_NUMBERNo active WhatsApp number connected
400TEMPLATE_NOT_APPROVEDTemplate exists but is not approved
400 / 502WHATSAPP_GRAPH_ERRORMeta Graph rejected the send (details may include Graph codes)
404NOT_FOUNDTemplate, contact, or message id not found
402PLAN_LIMIT_EXCEEDEDMonthly message (or other) plan limit reached
409IDEMPOTENCY_CONFLICTSame idempotency key used with a different body
429RATE_LIMIT_EXCEEDEDToo many requests
500INTERNAL_ERRORUnexpected server error

6. Rate limits

Applied on top of plan limits:

LimiterDefault
Per IP (outer)100 requests / 15 minutes
Per API key300 requests / 15 minutes (override with env PUBLIC_API_KEY_RATE_LIMIT)

Responses may include standard rate-limit headers (RateLimit-* / Retry-After depending on proxy).


7. cURL examples

Replace placeholders and your base URL.

Send a template with variables

curl -sS -X POST "https://flowziac.com/api/v1/public/messages/send" \
  -H "Content-Type: application/json" \
  -H "apiKey: pk_YOUR_KEY_ID" \
  -H "apiSecret: YOUR_SECRET" \
  -H "Idempotency-Key: order-INV-00516-send-1" \
  -d '{
    "type": "template",
    "phone": "919876543210",
    "phoneNumberId": "YOUR_FLOWZIAC_PHONE_ID",
    "templateName": "payment_thank_you",
    "templateLanguage": "en_US",
    "templateVariables": {
      "1": "Shree Balaji Traders",
      "2": "₹75,600.00",
      "3": "INV-00516",
      "4": "Hiren Enterprises"
    }
  }'

Send a template with body + dynamic URL button

When body {{1}} / {{2}} and URL button {{1}} need different values:

curl -sS -X POST "https://flowziac.com/api/v1/public/messages/send" \
  -H "Content-Type: application/json" \
  -H "apiKey: pk_YOUR_KEY_ID" \
  -H "apiSecret: YOUR_SECRET" \
  -d '{
    "type": "template",
    "phone": "919876543210",
    "phoneNumberId": "YOUR_FLOWZIAC_PHONE_ID",
    "templateName": "broadcast_offer_template",
    "templateLanguage": "en",
    "templateComponents": [
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Jane" },
          { "type": "text", "text": "Summer Deal" }
        ]
      },
      {
        "type": "button",
        "sub_type": "url",
        "index": "0",
        "parameters": [
          { "type": "text", "text": "YOUR_MAGIC_TOKEN" }
        ]
      }
    ]
  }'

Get message status

curl -sS "https://flowziac.com/api/v1/public/messages/cms9xrndw001jbj8etea2h8v1" \
  -H "apiKey: pk_YOUR_KEY_ID" \
  -H "apiSecret: YOUR_SECRET"

Send plain text

curl -sS -X POST "https://flowziac.com/api/v1/public/messages/send" \
  -H "Content-Type: application/json" \
  -H "apiKey: pk_YOUR_KEY_ID" \
  -H "apiSecret: YOUR_SECRET" \
  -d '{
    "type": "text",
    "phone": "919876543210",
    "content": "Your order has shipped."
  }'

List templates

curl -sS "https://flowziac.com/api/v1/public/templates" \
  -H "apiKey: pk_YOUR_KEY_ID" \
  -H "apiSecret: YOUR_SECRET"

8. Security practices

  • Treat apiSecret like a password: environment variables or a secrets manager, never client-side code or public repos.
  • Prefer HTTPS only in production.
  • Rotate keys periodically; revoke old keys in Settings when no longer needed.
  • Use Idempotency-Key for retries so network blips do not double-send.
  • Keep Meta WhatsApp webhooks configured so delivered / read / failed update message status in Flowziac.

9. Related

  • Internal authenticated routes (JWT + workspace) use /api/v1/workspaces/:workspaceId/...; the partner API above does not require a Bearer token.
  • WhatsApp template content and variable rules follow Meta’s policies (e.g. body length vs number of variables).
  • Dynamic URL buttons must use {{1}} at the end of the URL when creating templates; see Templates with buttons above.
  • With multiple active WhatsApp lines, always pass Flowziac phoneNumberId (see Common optional fields).
  • Partner API endpoints today: POST /messages/send, GET /messages/:messageId, GET /templates.