Skip to content
Splashify Pro
Docs

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

bash
# 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

json
{
  "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

json
{
  "response_id":   "uuid",
  "flow_id":       "1234567890123456",
  "phone_number":  "+919876543210",
  "contact_name":  "Alice Smith",
  "flow_token":    "opaque-meta-token-…",
  "response_json": { "email": "[email protected]", "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.

Sending a flow to a contact

Listing and syncing are the CLI's job; sending a flow is an API call:

POST

/api/v1/app/messages/send-flow

json
{
  "to":          "+919876543210",
  "flow_id":     "1234567890123456",
  "flow_token":  "your-correlation-token",
  "flow_cta":    "Record payment",
  "body":        "Tap below to file this expense",
  "flow_action": "navigate",
  "flow_action_payload": { "screen": "EXPENSE", "data": {} }
}

Publishing state

By default the message targets a published flow. Set "draft": true only while testing a flow that has not been published yet — sending a draft-mode message for a published flow is rejected by Meta, and vice versa.

Fixed 2026-08-14

Earlier builds mapped flow_id and flow_token directly onto Meta's interactive.action object instead of nesting them under action.parameters, so every call failed with Unexpected key "flow_id" on param "interactive.action".

The same build also omitted the required flow_message_version, wrote flow_cta into the footer instead of using it as the button label, and hardcoded mode: "draft" — which fails for any published flow. All four are corrected; no client change is needed beyond the optional new fields above.

Command reference

splashify flows — list

bash
splashify flows                                # default
splashify flows list                           # alias

Returns {success, flows: [...]} with the schema above. Newest first.

splashify flows sync — pull from Meta

bash
splashify flows sync
splashify flows refresh                        # alias

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:

json
{
  "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

bash
splashify 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:

bash
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

bash
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

bash
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

Response shape:

json
{
  "success":   true,
  "responses": [/* …response objects… */],
  "page":      1,
  "limit":     20,
  "total":     27
}

splashify flow response <response_id> — show one submission

bash
# 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-…

splashify flow <flow_id> deprecate — retire a flow

bash
splashify flow 1234567890123456 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

bash
splashify flows | jq '.flows[] | select(.response_count > 0) | {flow_id, name, response_count, status}'

Dump every submission on a flow to a JSON file

bash
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)

bash
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

bash
PHONE="+919876543210"
splashify flow 1234567890123456 responses --limit 200 | \
  jq --arg p "$PHONE" '.responses[] | select(.phone_number == $p)'

Sync nightly via cron

bash
# 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

bash
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"

bash
# 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

text
┌─────────────┐    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.