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
- Sign in to the Flowziac dashboard.
- Open Settings (workspace settings).
- Find the API keys section (visible to workspace owners and admins only).
- Optionally enter a label, then click Generate key.
- Copy
apiKeyandapiSecretimmediately. 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 URL | https://flowziac.com/api/v1/public |
3. Authentication
Send these headers on every request:
| Header | Alternative | Value |
|---|---|---|
apiKey | X-API-Key | Public key id (starts with pk_…) |
apiSecret | X-API-Secret | Secret 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), orcontactId: an existing contact id in that workspace.
Common optional fields
| Field | Description |
|---|---|
phoneNumberId | Internal 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). |
idempotencyKey | Optional 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. |
revalidateFlowSession | Optional boolean. When true and you send with contactId, closes any active flow session for that contact (optionally scoped to the same line) before sending. |
bodyParameters | Optional 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’sid
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"
}
}
templateLanguageis optional; it helps match the stored template if you use locale codes (e.g.envsen_US). When usingtemplateComponents, 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
templateVariablesfor most integrations. UsetemplateComponentsonly when you need full Meta Cloud API control (see below). - For
type: "template", you must provide at least one of:templateVariables,templateComponents,bodyParameters, orcarousel.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 type | What 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, URLhttps://…). - 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" }
]
}
]
}
indexis 0-based button order on the template ("0"= first button).parameters[].textfor 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
templateComponentsis provided, Flowziac forwards them to Meta and does not rebuild components fromtemplateVariables.
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
templateVariablesmapping: Main bubble{{1}},{{2}}map to keys"1","2"as usual. For each carousel card, placeholders use keysc{cardIndex}_{slot}— e.g. card 0’s{{1}}isc0_1, card 1’s{{1}}isc1_1(same pattern for URL variables on card buttons). Card header media ids usec{cardIndex}_header. - Structured
carouselpayload (optional): Instead of only flattemplateVariables, you can sendcarousel.cards[]withbodyValues,buttonValues, andheader. 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). OptionalbodyParametersoverrides main-bubble numeric slots ("1","2") aftertemplateVariables.
Card header media — provide one of:
| Field | Description |
|---|---|
header.link | Public image/video URL. Flowziac auto-uploads it to WhatsApp before sending. (Recommended) |
header.id | Pre-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
templateComponentswithbodyandcarouselcomponents 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
}
}
| Field | Meaning |
|---|---|
id | Flowziac message id — use with Get message status |
status | Uppercased DB status (SENT, DELIVERED, READ, FAILED, …) |
whatsappMessageId | Meta wamid when Graph accepted the send |
deliveredAt / readAt | Filled when Meta status webhooks update the message |
idempotentReplay | true 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 |
|---|---|
SENT | Accepted by Meta; delivery not confirmed in Flowziac yet |
DELIVERED | Device received the message (deliveredAt set) |
READ | User opened the message (readAt set) |
FAILED | Delivery 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 found → 404 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.)
| HTTP | error code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing/invalid/revoked apiKey / apiSecret |
| 400 | PHONE_NUMBER_REQUIRED | Multiple active lines and no phoneNumberId |
| 400 | PHONE_NUMBER_NOT_FOUND | phoneNumberId not in this workspace |
| 400 | PHONE_NUMBER_INACTIVE | Requested line is inactive |
| 400 | NO_PHONE_NUMBER | No active WhatsApp number connected |
| 400 | TEMPLATE_NOT_APPROVED | Template exists but is not approved |
| 400 / 502 | WHATSAPP_GRAPH_ERROR | Meta Graph rejected the send (details may include Graph codes) |
| 404 | NOT_FOUND | Template, contact, or message id not found |
| 402 | PLAN_LIMIT_EXCEEDED | Monthly message (or other) plan limit reached |
| 409 | IDEMPOTENCY_CONFLICT | Same idempotency key used with a different body |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
| 500 | INTERNAL_ERROR | Unexpected server error |
6. Rate limits
Applied on top of plan limits:
| Limiter | Default |
|---|---|
| Per IP (outer) | 100 requests / 15 minutes |
| Per API key | 300 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
apiSecretlike 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-Keyfor retries so network blips do not double-send. - Keep Meta WhatsApp webhooks configured so
delivered/read/failedupdate 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.