WhatsApp Flows
The flows and flow commands mirror the /flows page in the app —
list your WhatsApp Flow forms, sync them from Meta, read submissions, and
mark old ones deprecated. The same “Sync from Meta” and “Create in
Meta” buttons you see on the page are exposed as CLI subcommands.
Backed by /api/v1/app/flows/*. Every call uses your stored oc_live_
token and is scoped to your account.
WhatsApp Flow builder is graphical-only. Meta only exposes the Flow JSON editor through its in-browser Flow Builder — neither the app nor the CLI can create the actual flow JSON. The CLI gives you the create URL (
splashify flows create-url) to open in a browser; once the flow is published in Meta,splashify flows syncpulls it down to your account.
Quick start
# 1. Pull your flows from Meta (run after creating/editing in the Flow Builder)
splashify flows sync
# 2. See what's there
splashify flows
# 3. Look at submissions on a specific flow
splashify flow 1234567890123456 responses
# 4. When a flow is end-of-life, deprecate it
splashify flow 1234567890123456 deprecateWhat a flow looks like
{
"flow_id": "1234567890123456",
"name": "Lead capture form",
"categories": ["SIGN_UP", "LEAD_GENERATION"],
"status": "PUBLISHED", // DRAFT | PUBLISHED | DEPRECATED | BLOCKED
"json_version": "3.1",
"validation_errors": "",
"preview_url": "https://business.facebook.com/wa/manage/flows/…/preview/…",
"preview_expires_at": "2026-05-28T12:00:00Z",
"created_at": "2026-05-12T09:30:11Z",
"updated_at": "2026-05-19T14:22:08Z",
"response_count": 27
}response_count is computed by the backend on every list call — it’s the
number of submissions stored locally for this flow (not Meta’s count).
What a flow response looks like
{
"response_id": "uuid",
"flow_id": "1234567890123456",
"phone_number": "+919876543210",
"contact_name": "Alice Smith",
"flow_token": "opaque-meta-token-…",
"response_json": { "email": "alice@x.com", "company": "Acme" }, // user-defined fields
"wa_message_id": "wamid.HBgN…",
"received_at": "2026-05-20T14:00:00Z",
"created_at": "2026-05-20T14:00:01Z"
}The response_json shape varies per flow — it carries whatever screens +
fields the flow author designed in the Flow Builder.
Command reference
splashify flows — list
splashify flows # default
splashify flows list # alias| Backed by | GET /api/v1/app/flows |
|---|
Returns {success, flows: [...]} with the schema above. Newest first.
splashify flows sync — pull from Meta
splashify flows sync
splashify flows refresh # alias| Backed by | POST /api/v1/app/flows/sync |
|---|
This is the “Sync from Meta” button from the page. The backend reaches
out to Meta’s Flows Graph API, downloads the latest snapshot of every
flow on your WABA, and upserts the rows into app_flows. Response:
{
"success": true,
"message": "Synced 3 flows from Meta",
"count": 3,
"flows": [{"flow_id": "...", "name": "..."}]
}Run this after editing a flow in the Flow Builder — until you sync, the listing reflects the previous snapshot. Sync is idempotent and safe to run on a cron.
splashify flows create-url — open Meta’s Flow Builder
splashify flows create-url| Backed by | GET /api/v1/app/flows/create-url |
|---|
This is the “Create in Meta” button. The endpoint returns the deep-link URL into Meta’s in-browser Flow Builder, pre-scoped to your WABA. Open it in a browser:
URL=$(splashify flows create-url | jq -r '.url')
echo "$URL"
# Then on macOS:
splashify flows create-url | jq -r '.url' | xargs open
# Linux: | xargs xdg-open
# Windows: | xargs startAfter you publish the new flow in Meta, run splashify flows sync to
pull it down.
splashify flow <flow_id> — show one flow
splashify flow 1234567890123456No dedicated backend endpoint — the CLI fetches the list and filters
client-side, same pattern as splashify member <id>, splashify canned <id>, etc.
splashify flow <flow_id> responses — list submissions
splashify flow 1234567890123456 responses # page 1, 20 per page
splashify flow 1234567890123456 responses --page 2
splashify flow 1234567890123456 responses --limit 100
splashify flow 1234567890123456 submissions # alias| Backed by | GET /api/v1/app/flows/:flow_id/responses?page=N&limit=N |
|---|
Response shape:
{
"success": true,
"responses": [/* …response objects… */],
"page": 1,
"limit": 20,
"total": 27
}splashify flow response <response_id> — show one submission
# Top-level form (the backend endpoint takes only response_id)
splashify flow response 7a32bce6-910d-4b1f-…
# Nested form — same backend call, just clearer when you have both ids handy
splashify flow 1234567890123456 response 7a32bce6-910d-4b1f-…| Backed by | GET /api/v1/app/flows/responses/:response_id |
|---|
splashify flow <flow_id> deprecate — retire a flow
splashify flow 1234567890123456 deprecate| Backed by | POST /api/v1/app/flows/:flow_id/deprecate |
|---|
Marks the flow as DEPRECATED on Meta + in your local row. A deprecated
flow can no longer be sent to users in new messages, but historic
submissions are preserved.
There is no undo from the CLI. Once deprecated, restoring a flow requires creating a new version in Meta’s Flow Builder. The local row stays for record-keeping.
Common workflows
Show only flows with submissions
splashify flows | jq '.flows[] | select(.response_count > 0) | {flow_id, name, response_count, status}'Dump every submission on a flow to a JSON file
FLOW=1234567890123456
PAGE=1
LIMIT=100
ALL="[]"
while : ; do
PAYLOAD=$(splashify flow "$FLOW" responses --page "$PAGE" --limit "$LIMIT")
THIS=$(echo "$PAYLOAD" | jq '.responses // []')
ALL=$(jq -s 'add' <(echo "$ALL") <(echo "$THIS"))
COUNT=$(echo "$THIS" | jq 'length')
[ "$COUNT" -lt "$LIMIT" ] && break
PAGE=$((PAGE+1))
done
echo "$ALL" | jq '.' > "flow-$FLOW-responses.json"
echo "Saved $(jq 'length' < flow-$FLOW-responses.json) responses"Drop submissions into a CSV (whatever shape response_json has)
splashify flow 1234567890123456 responses --limit 100 | \
jq -r '
.responses[] |
[.response_id, .phone_number, .contact_name, .received_at,
(.response_json | to_entries | map("\(.key)=\(.value)") | join("|"))]
| @csv
' > flow-export.csvFind a submission by phone number
PHONE="+919876543210"
splashify flow 1234567890123456 responses --limit 200 | \
jq --arg p "$PHONE" '.responses[] | select(.phone_number == $p)'Sync nightly via cron
# crontab entry — runs at 03:15 every night
15 3 * * * /usr/local/bin/splashify flows sync >/var/log/splashify-flows-sync.log 2>&1The endpoint is idempotent — running it more often is harmless if your team edits flows mid-day.
Deprecate every DRAFT flow older than 30 days
CUTOFF=$(date -u -v-30d '+%Y-%m-%dT%H:%M:%SZ') # macOS
# CUTOFF=$(date -u -d '30 days ago' '+%Y-%m-%dT%H:%M:%SZ') # linux
splashify flows | \
jq -r --arg c "$CUTOFF" '
.flows[]
| select(.status == "DRAFT" and .created_at < $c)
| .flow_id
' | \
xargs -I{} splashify flow {} deprecateAudit “validation_errors”
# Any flow that Meta is complaining about
splashify flows | jq '.flows[] | select(.validation_errors != "") |
{flow_id, name, status, validation_errors}'How the data lifecycle works
┌─────────────┐ splashify flows create-url ┌──────────────────┐
│ You │ ─────────────────────────────► │ Meta Flow Builder│
│ (terminal) │ open in browser │ (graphical) │
└─────────────┘ └────────┬─────────┘
│ publish
▼
┌──────────────┐
│ Meta Graph │
└──────┬───────┘
│
splashify flows sync │
◄────────────────────────┘
│
▼
┌────────────────────┐
│ app_flows table │ ◄── splashify flows
│ app_flow_responses│ ◄── splashify flow … responses
└────────────────────┘
▲
(Meta webhooks push │
new submissions in
real time — sync is for
flow definition only)- Definitions (the flow JSON + status + categories) flow Meta → us,
and you trigger the pull with
splashify flows sync. - Submissions flow Meta → us in real time via webhooks (no sync
required) — they always show up under
splashify flow … responsesas soon as the user taps Submit in WhatsApp.
Troubleshooting
“WhatsApp Not Connected” — the CLI’s account doesn’t have a WABA yet. The flows endpoints require a connected WABA; finish the Meta Embedded Signup from the app’s Dashboard, then retry. Same gate as the page’s WABA check.
flows sync returns 0 flows but Meta has some — the sync call is
scoped to your WABA; if Meta has flows on a different WABA (e.g. a
sandbox account), they won’t show up. Confirm the WABA id you expect
with splashify waba and splashify waba sync first.
response_count: 0 on a flow that has submissions in Meta — the
local app_flow_responses table only fills via the webhook listener.
If you connected the WABA after a submission already happened, that
submission is lost — Meta does not back-fill webhooks. New submissions
from this point on will arrive normally.
flow … deprecate returns “validation_errors” — Meta refuses to
deprecate a flow that’s in an inconsistent state (e.g. mid-publish).
Wait a minute and retry, or open the flow in the Flow Builder and fix
the validation errors first.
flows create-url returns a URL but Meta says “permission denied” —
the URL embeds your business_id; the user clicking it must be an admin
on the Meta Business that owns the WABA. Make sure you’re signed into
the right Business in business.facebook.com before opening the URL.
See also
splashify waba— manage the connected WABA (the prerequisite for flows).splashify templates— approved WhatsApp templates, the other Meta-managed artifact (flows are messages with a form; templates are pre-approved message bodies).splashify message media— the send-side commands you’d pair with a flow when triggering it via template messages.