Skip to Content
splashify CLIWhatsApp Flows

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 sync pulls 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 deprecate

What 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 byGET /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 byPOST /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 byGET /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 start

After you publish the new flow in Meta, run splashify flows sync to pull it down.

splashify flow <flow_id> — show one flow

splashify flow 1234567890123456

No 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 byGET /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 byGET /api/v1/app/flows/responses/:response_id

splashify flow <flow_id> deprecate — retire a flow

splashify flow 1234567890123456 deprecate
Backed byPOST /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.csv

Find 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>&1

The 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 {} deprecate

Audit “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 … responses as 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.