Flows
22 endpoints in this family.
List Flow responses
GET/api/v1/flow-responses
Returns what contacts filled in on WhatsApp Flows, most recent first. Read from the received messages, so kept as long as message content is (see your plan's retention): store what you need through the message.received webhook (data.reply). Use flowId for the answers to one Flow, flowToken for the answer to one particular send.
Query parameters
flowIdstringOnly the answers to this Flow (its Nodium id), whether it was sent as a
flowform or opened from a template button. An unknown Flow answers404.flowTokenstringOnly the answers to sends made with this
flowToken.fromstringOnly answers at or after this instant (ISO 8601).
tostringOnly answers before this instant (ISO 8601).
limitintegerHow many to return.
cursorstringOpaque cursor from the previous page.
Response
A page of Flow answers.
data.itemsobject[]Each:
messageId,conversationId,contact(id,name,identity),flowToken,data,createdAt.data.nextCursorstringPass it as
cursorfor the next page;nullat the end.data.hasMorebooleanWhether another page exists.
data.totalintegerHow many match, on the first page only;
nullafter.
curl https://nodium.io/api/v1/flow-responses \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ], "nextCursor": "β¦", "hasMore": true, "total": 0 }}List WhatsApp Flows
GET/api/v1/flows
Returns the WhatsApp Flows (interactive forms) of the client you act on, most recently changed first. A Flow is static until you attach a data endpoint, then dynamic. To send one, use its metaFlowId in a flow form (POST /messages, type: "interactive"). To pull in Flows drawn directly at Meta, call POST /flows/sync first.
Query parameters
statusstringOnly Flows in this state.
draftpublisheddeprecatedblockedthrottledmodestringOnly static or only dynamic Flows.
staticdynamicqstringSearch by name.
Response
The Flows of the client.
data.itemsobject[]The Flows, most recently changed first.
Show fields
idstringThe Nodium id.
metaFlowIdstringThe id at Meta: the
flowIdof aflowform.wabaIdstringThe WhatsApp Business account it lives in.
namestringIts name.
statusstringdraft,published(sendable, cannot change),deprecated,blockedorthrottled(unhealthy endpoint).draftpublisheddeprecatedblockedthrottledmodestringdynamiconce a data endpoint is attached.staticdynamiccategoriesstring[]Its Meta categories.
responsesintegerHow many answers it has received, while message content is kept (
GET /flow-responses?flowId=).createdAtstringISO 8601.
updatedAtstringISO 8601.
curl https://nodium.io/api/v1/flows \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ { "id": "β¦", "metaFlowId": "β¦", "wabaId": "β¦", "name": "β¦", "status": "draft", "mode": "static", "categories": [], "responses": 0, "createdAt": "β¦", "updatedAt": "β¦" } ] }}Create a WhatsApp Flow
POST/api/v1/flows
Creates a draft Flow at Meta, in a WhatsApp Business account of the client, and records its first version. Without flowJson the draft starts from a four-screen quote request. Meta validates the JSON: its errors come back in validationErrors and the draft exists anyway. The client needs at least one connected number; if it has several WhatsApp accounts, name one with wabaId.
Body
namestringrequiredA name, 1 to 200 characters.
categoriesstring[]One or more of SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER. Default
OTHER.channelIdstringA connected number of the client: the Flow is created in its WhatsApp Business account.
wabaIdstringThe WhatsApp Business account, when the client has several (ignored when
channelIdis given).flowJsonobjectThe Flow JSON (10 MB at most). Default: a sample quote request.
Response
The draft Flow, its first version and Meta's validation errors.
data.flowobjectThe Flow (see
GET /flows).data.versionobjectIts first version:
label,sha,validationErrors,createdAt,flowJson.data.validationErrorsobject[]What Meta found wrong in the JSON; empty when it is valid.
Refusals
- 409
no_channelThe client has no connected WhatsApp number. - 502
upstream_errorMeta refused or did not answer.
curl -X POST https://nodium.io/api/v1/flows \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "β¦" }'{ "data": { "flow": {}, "version": {}, "validationErrors": [ {} ] }}Delete a draft Flow
DEL/api/v1/flows/{id}
Deletes a draft Flow at Meta and here, with its versions. A published Flow cannot be deleted: deprecate it instead.
Path parameters
idstringThe Flow id.
Response
Confirmation that the draft is gone.
data.deletedbooleanAlways
true.
Refusals
- 409
conflictThe Flow is published, deprecated, blocked or throttled: only a draft can be deleted. - 502
upstream_errorMeta refused or did not answer.
curl -X DELETE https://nodium.io/api/v1/flows/<id> \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "deleted": true }}Get a WhatsApp Flow
GET/api/v1/flows/{id}
Returns the Flow, its latest version with the JSON and Meta's validation errors, and its data endpoint when it is dynamic (the address Meta calls on Nodium, and yours). The signing secret is never returned.
Path parameters
idstringThe Flow id.
Response
The Flow, its latest version and its data endpoint.
data.flowobjectThe Flow (see
GET /flows).data.versionobjectThe latest version (
label,sha,flowJson,validationErrors,publishedAt,createdAt);nullbefore the first.data.dataEndpointobjecturl(yours) andmetaUrl(the address Meta calls);nullfor a static Flow.
curl https://nodium.io/api/v1/flows/<id> \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "flow": {}, "version": {}, "dataEndpoint": {} }}Rename a WhatsApp Flow
PATCH/api/v1/flows/{id}
Changes the name or the categories of a Flow, at Meta and here. Send only what changes. A deprecated Flow cannot be changed.
Path parameters
idstringThe Flow id.
Body
namestringA new name, 1 to 200 characters.
categoriesstring[]One or more of SIGN_UP, SIGN_IN, APPOINTMENT_BOOKING, LEAD_GENERATION, CONTACT_US, CUSTOMER_SUPPORT, SURVEY, OTHER.
Response
The Flow as it now is.
data.flowobjectThe Flow (see
GET /flows).
Refusals
- 409
conflictThe Flow is deprecated. - 502
upstream_errorMeta refused or did not answer.
curl -X PATCH https://nodium.io/api/v1/flows/<id> \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "β¦", "categories": [ "β¦" ] }'{ "data": { "flow": {} }}Detach the data endpoint of a Flow
DEL/api/v1/flows/{id}/data-endpoint
Makes the Flow static again: Meta no longer calls an endpoint and Nodium stops relaying. Only a draft can change.
Path parameters
idstringThe Flow id.
Response
Confirmation that the Flow is static.
data.removedbooleanAlways
true.
Refusals
- 409
conflictThe Flow is not a draft. - 502
upstream_errorMeta refused or did not answer.
curl -X DELETE https://nodium.io/api/v1/flows/<id>/data-endpoint \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "removed": true }}Get the data endpoint of a Flow
GET/api/v1/flows/{id}/data-endpoint
Returns the data endpoint of a dynamic Flow: url (yours, where Nodium relays) and metaUrl (the address Meta calls on Nodium). null for a static Flow. The signing secret is never returned.
Path parameters
idstringThe Flow id.
Response
The data endpoint, or null.
data.dataEndpointobjecturlandmetaUrl;nullwhen the Flow is static.
curl https://nodium.io/api/v1/flows/<id>/data-endpoint \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "dataEndpoint": {} }}Attach a data endpoint to a Flow
PUT/api/v1/flows/{id}/data-endpoint
Makes the Flow dynamic. Meta calls Nodium on every screen; Nodium decrypts the request and relays it to your url as a signed POST { source, flow, data_exchange: { version, action, screen, data, flow_token }, signature_valid, received_at } (header X-Nodium-Signature, same scheme as webhooks); your JSON answer { screen, data } is encrypted and returned to Meta. You have about 9 seconds to answer. Nodium also generates the encryption key of the client and registers it on the client's numbers. The signing secret is returned once, here. Only a draft can change.
Path parameters
idstringThe Flow id.
Body
urlstringrequiredYour https address, up to 2000 characters.
Response
The data endpoint and its signing secret β shown only here.
data.dataEndpointobjecturlandmetaUrl.data.secretstringThe signing secret,
whsec_β¦. Shown only here.data.unregisteredChannelsstring[]Numbers that did not take the encryption key; call this route again once they are fixed.
Refusals
- 409
conflictThe Flow is not a draft. - 422
webhook_url_not_httpsThe address must start with https://. - 502
upstream_errorMeta refused or did not answer.
curl -X PUT https://nodium.io/api/v1/flows/<id>/data-endpoint \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "β¦" }'{ "data": { "dataEndpoint": {}, "secret": "β¦", "unregisteredChannels": [ "β¦" ] }}Try the data endpoint of a Flow
POST/api/v1/flows/{id}/data-endpoint-test
Sends your address a signed INIT call (the one Meta makes when the form opens), marked test: true, and returns the HTTP status, the duration and your answer (cut at 4,000 characters). It does not go through Meta and is not listed in the calls of the Flow.
Path parameters
idstringThe Flow id.
Response
What your address answered.
data.okbooleanA 2xx answer whose body is a JSON object.
data.statusintegerThe HTTP status;
nullwhen nothing answered.data.durationMsintegerHow long it took.
data.bodystringYour answer, or the reason it failed.
Refusals
- 409
conflictThe Flow has no data endpoint.
curl -X POST https://nodium.io/api/v1/flows/<id>/data-endpoint-test \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "ok": true, "status": 0, "durationMs": 0, "body": "β¦" }}Deprecate a Flow
POST/api/v1/flows/{id}/deprecate
Retires a published Flow: it can no longer be sent or opened. Messages already sent stay in the conversations. It cannot be undone.
Path parameters
idstringThe Flow id.
Response
The deprecated Flow.
data.flowobjectThe Flow, now
deprecated.
Refusals
- 409
conflictOnly a published Flow can be deprecated. - 502
upstream_errorMeta refused or did not answer.
curl -X POST https://nodium.io/api/v1/flows/<id>/deprecate \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "flow": {} }}Duplicate a Flow
POST/api/v1/flows/{id}/duplicate
Copies the Flow at Meta into a new draft in the same WhatsApp Business account, with the same JSON. This is how you change a published Flow. The data endpoint is not copied: attach it again on the copy.
Path parameters
idstringThe Flow id.
Body
namestringA name for the copy. Default: the name followed by
(copy).
Response
The new draft.
data.flowobjectThe new draft Flow (see
GET /flows).
Refusals
- 502
upstream_errorMeta refused or did not answer.
curl -X POST https://nodium.io/api/v1/flows/<id>/duplicate \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "β¦" }'{ "data": { "flow": {} }}Get the sending health of a Flow
GET/api/v1/flows/{id}/health
Asks Meta whether the Flow can be sent right now (AVAILABLE, LIMITED or BLOCKED), and for each entity involved (the Flow, the WhatsApp Business account, the business, the app) what blocks it and how to fix it.
Path parameters
idstringThe Flow id.
Response
What Meta says about sending this Flow.
data.canSendMessagestringAVAILABLE,LIMITED,BLOCKED, ornullwhen Meta says nothing.data.entitiesobject[]Each entity:
type,id,canSendMessage,errors(code,description,solution).
Refusals
- 502
upstream_errorMeta refused or did not answer.
curl https://nodium.io/api/v1/flows/<id>/health \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "canSendMessage": "β¦", "entities": [ {} ] }}List the calls Meta made to a Flow
GET/api/v1/flows/{id}/invocations
The last 100 calls Meta made to a dynamic Flow, most recent first: the action (INIT, data_exchange, BACK, ping), the screen, the HTTP status your endpoint answered, how long it took, and the error if any. The content exchanged is never kept. Calls are kept thirty days.
Path parameters
idstringThe Flow id.
Response
The last calls.
data.itemsobject[]id,action,screen,statusCode,durationMs,error,createdAt.
curl https://nodium.io/api/v1/flows/<id>/invocations \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ] }}Get the preview of a Flow
GET/api/v1/flows/{id}/preview
Returns the address of the preview page Meta renders for the Flow β the form as it looks on a phone, with Android and iOS, light and dark modes to choose from. The address expires after about thirty days; ask again when it does.
Path parameters
idstringThe Flow id.
Response
The preview address.
data.previewUrlstringThe address to open or embed.
data.expiresAtstringWhen it stops working, ISO 8601, or
null.
Refusals
- 502
upstream_errorMeta refused or did not answer.
curl https://nodium.io/api/v1/flows/<id>/preview \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "previewUrl": "β¦", "expiresAt": "β¦" }}Publish a Flow
POST/api/v1/flows/{id}/publish
Publishes the draft so it can be sent to customers. This is final: a published Flow can no longer be changed or unpublished β duplicate it to change it, deprecate it to stop it. Meta refuses to publish JSON that still has validation errors, and says why.
Path parameters
idstringThe Flow id.
Response
The published Flow.
data.flowobjectThe Flow, now
published.
Refusals
- 409
conflictThe Flow is not a draft. - 502
upstream_errorMeta refused (the JSON is invalid, for example) or did not answer.
curl -X POST https://nodium.io/api/v1/flows/<id>/publish \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "flow": {} }}List the versions of a Flow
GET/api/v1/flows/{id}/versions
Every JSON saved for the Flow, most recent first: label (v1, v2β¦), sha, Meta's validationErrors, and publishedAt on the version Meta published. Versions are immutable. The JSON itself is left out: read one with GET /flows/{id}/versions/{versionId}.
Path parameters
idstringThe Flow id.
Response
The versions of the Flow.
data.itemsobject[]The versions, most recent first, without
flowJson.
curl https://nodium.io/api/v1/flows/<id>/versions \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ] }}Save a new version of a Flow
POST/api/v1/flows/{id}/versions
Sends the JSON to Meta, which validates it, and records it as a new version. Invalid JSON is saved too: validationErrors says what to fix before publishing. Saving the same JSON as the latest version does not open a new one. Only a draft can change; to change a published Flow, duplicate it first.
Path parameters
idstringThe Flow id.
Body
flowJsonobjectrequiredThe Flow JSON (10 MB at most).
Response
The version and Meta's validation errors.
data.versionobjectlabel,sha,flowJson,validationErrors,createdAt.data.validationErrorsobject[]What Meta found wrong; empty when the JSON is valid.
Refusals
- 409
conflictThe Flow is not a draft. - 502
upstream_errorMeta refused or did not answer.
curl -X POST https://nodium.io/api/v1/flows/<id>/versions \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "flowJson": {} }'{ "data": { "version": {}, "validationErrors": [ {} ] }}Get one version of a Flow
GET/api/v1/flows/{id}/versions/{versionId}
Returns one saved version with its JSON and the validation errors Meta gave it.
Path parameters
idstringThe Flow id.
versionIdstringThe version id.
Response
The version.
data.versionobjectlabel,sha,flowJson,validationErrors,publishedAt,createdAt.
curl https://nodium.io/api/v1/flows/<id>/versions/<versionId> \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "version": {} }}Read the encryption state of the client's numbers
GET/api/v1/flows/encryption
Meta encrypts the calls to a dynamic Flow with a key registered per phone number. Returns each number of the client with state: ready (the client's key is registered), missing (to register with POST /flows/encryption) or unknown (Meta did not answer).
Response
The encryption state of each number.
data.itemsobject[]channelId,name,phone,state.
curl https://nodium.io/api/v1/flows/encryption \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ] }}Register the encryption key on a number
POST/api/v1/flows/encryption
Creates the client's encryption key if it has none, then registers its public part at Meta on one number (channelId) or on all of the client's numbers. It is a one-time step per number. Returns the state of each number.
Body
channelIdstringA number of the client. Omit to register on all of them.
Response
The encryption state of each number.
data.itemsobject[]channelId,name,phone,state.
Refusals
- 404
not_foundThe number is not one of the client's.
curl -X POST https://nodium.io/api/v1/flows/encryption \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -d '{ "channelId": "β¦" }'{ "data": { "items": [ {} ] }}Sync Flows from Meta
POST/api/v1/flows/sync
Reads the Flows of the client's WhatsApp Business accounts at Meta and records them here: Flows drawn directly in WhatsApp Manager show up, the others get their name, status and categories refreshed. Nothing is created or changed at Meta, and a Flow that no longer exists there is kept.
Response
The Flows of the client after the sync.
data.itemsobject[]The Flows of the client (see
GET /flows).data.syncedintegerHow many Flows were read at Meta.
Refusals
- 502
upstream_errorMeta refused or did not answer.
curl -X POST https://nodium.io/api/v1/flows/sync \ -H "Authorization: Bearer $NODIUM_KEY"{ "data": { "items": [ {} ], "synced": 0 }}