> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ycloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Implement a WhatsApp Flow endpoint

> Handle Flow health checks, error notifications, data exchange, navigation, and completion through YCloud.

Use a Flow endpoint when you need to load screens dynamically or process data
submitted by a WhatsApp user. Configure your public HTTPS URL as `endpointUri`
when you create a Flow or update its metadata.

This guide describes the plain JSON requests YCloud forwards to your endpoint.
It does not describe a direct connection to Meta's encrypted data endpoint.
See [Manage WhatsApp Flows](/en/api-reference/guides/whatsapp-platform/manage-whatsapp-flows)
for Flow creation, preview, publishing, and lifecycle management.

## Before you begin

* Expose a public HTTPS endpoint that accepts `POST` requests.
* Return JSON within 15 seconds.
* Define the screens and their data fields in your Flow JSON.
* Generate a `flow_token` when sending the Flow message so you can correlate
  the interaction with your application session.
* Use server-side validation before accepting submitted data.

## Request flow

1. The user opens or interacts with a Flow in WhatsApp.
2. YCloud forwards a JSON request to your configured endpoint.
3. Your endpoint reads `action` and processes the request.
4. Your JSON response selects a screen and supplies its data, or completes the Flow.

## Handle a health check

A health check contains `action: ping`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "action": "ping"
}
```

Return:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "status": "active"
  }
}
```

Keep this path lightweight. Do not perform a business transaction during a health check.

## Handle an error notification

Error notifications include `data.error` and `data.error_message`. They can use
`INIT` or `data_exchange` as the action. Check for this error data before routing
ordinary requests by action.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "version": "3.0",
  "flow_token": "FLOW_SESSION_TOKEN",
  "action": "data_exchange",
  "data": {
    "error": "ERROR_KEY",
    "error_message": "Error details"
  }
}
```

Record the error for investigation and return an acknowledgement:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "acknowledged": true
  }
}
```

## Handle data exchange

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "version": "3.0",
  "screen": "DETAILS",
  "action": "data_exchange",
  "data": {
    "email": "customer@example.com"
  },
  "flow_token": "FLOW_SESSION_TOKEN"
}
```

| Field | Meaning |
| - | - |
| `version` | Data API version, `3.0` in these requests. |
| `action` | `INIT` when opening the Flow, `data_exchange` when submitting a screen, or `BACK` when navigating back. |
| `screen` | Current screen ID. It can be absent for `INIT` or `BACK`. Do not name a Flow screen `SUCCESS`; that value is reserved for completion. |
| `data` | Screen fields or submitted input. It can be absent for `INIT` or `BACK`. |
| `flow_token` | Session token you supplied in the Flow message. |

Handle each action according to the screens you have defined:

| Action | Response behavior |
| - | - |
| `INIT` | Return the initial screen and its starting data. |
| `data_exchange` | Validate the submitted data, then return the next screen or a validation error on the same screen. |
| `BACK` | Return the previous screen with the data it needs. |

### Navigate to a screen

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "screen": "CONFIRMATION",
  "data": {
    "user_email": "customer@example.com"
  }
}
```

The `screen` must exist in your Flow JSON. Its declared data schema must accept
the fields in `data`.

### Return a validation error

Stay on the current screen and return an error field that your screen displays:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "screen": "DETAILS",
  "data": {
    "error_message": "Please enter a valid email address."
  }
}
```

### Complete the Flow

Return `screen: SUCCESS` with `extension_message_response.params`. Include the
original `flow_token` and any additional result fields you want in the Flow
response message.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "screen": "SUCCESS",
  "data": {
    "extension_message_response": {
      "params": {
        "flow_token": "FLOW_SESSION_TOKEN",
        "appointment_id": "APPOINTMENT_ID"
      }
    }
  }
}
```

This closes the Flow and sends a Flow response message to the chat. Parse the
result from the [inbound Flow response webhook](/en/api-reference/guides/examples/webhook-examples/whatsapp-inbound-message-webhook-examples#inbound-interactive-flow-response-message).

## Implementation example

This Express example handles all three request categories. Match the screen
IDs and response fields to your own Flow JSON. Mount any endpoint access
controls used by your deployment before this handler.

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import express from "express";

const app = express();
app.use(express.json({ limit: "256kb" }));

app.post("/flow-endpoint", (req, res) => {
  if (!req.body || typeof req.body !== "object" || Array.isArray(req.body)) {
    return res.status(400).json({ error: "Expected a JSON object" });
  }
  const { action, screen, flow_token: flowToken } = req.body;
  const data = req.body.data ?? {};

  if (action === "ping") {
    return res.json({ data: { status: "active" } });
  }
  if (data.error) {
    // Record the error without logging sensitive form data or session tokens.
    return res.json({ data: { acknowledged: true } });
  }
  if (!flowToken) {
    return res.status(400).json({ error: "Missing flow_token" });
  }
  if (action === "INIT" || action === "BACK") {
    return res.json({ screen: "DETAILS", data: {} });
  }
  if (action === "data_exchange" && screen === "DETAILS") {
    if (typeof data.email !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
      return res.json({
        screen: "DETAILS",
        data: { error_message: "Please enter a valid email address." }
      });
    }
    return res.json({ screen: "CONFIRMATION", data: { user_email: data.email } });
  }
  if (action === "data_exchange" && screen === "CONFIRMATION") {
    return res.json({
      screen: "SUCCESS",
      data: { extension_message_response: { params: { flow_token: flowToken } } }
    });
  }
  return res.status(400).json({ error: "Unsupported action or screen" });
});

app.listen(3000);
```

## Verify the endpoint

Test `ping`, error acknowledgement, `INIT` without `screen` or `data`, valid
and invalid submissions, `BACK`, and `SUCCESS` completion. Check the 15-second
response limit and confirm that the completion webhook carries your original
`flow_token`. Preview the Flow before publishing it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.