Skip to main content

Managed Endpoint and Data Sources

A dynamic flow talks to a data endpoint while the contact fills it in. With SendSeven you never host that endpoint yourself. SendSeven runs it for every dynamic flow, handles WhatsApp's encryption and signatures, validates submissions, and loads data from SendSeven or from your own backend.

You only describe what data a screen needs, using data bindings in the builder doc.

Data bindings​

{
"data_sources": [
{
"id": "slots",
"kind": "webhook",
"screen_id": "PICK_SLOT",
"data_source_id": "e4b1a0c2-…",
"params": { "service": "haircut" },
"outputs": { "slots": "available_slots" },
"fallback": { "slots": [{ "id": "call_me", "title": "Bitte rufen Sie mich an" }] }
}
]
}
FieldNotes
idUnique within the doc. Sent to your webhook as binding_id.
kindstatic (fixed value), internal (a SendSeven resolver), webhook (your HTTPS endpoint, Scale+). connector is reserved for upcoming integrations and is not available yet.
screen_idThe screen whose data this binding fills.
resolverkind: internal only. See Built-in resolvers.
data_source_idkind: webhook only. A data source created with the data source API.
paramsFree-form object passed to the resolver or webhook.
valuekind: static only. The data object to use.
outputsMaps a screen data key to a path in the result: { "slots": "available_slots" }. An empty path ("") means "same key". Without outputs, every key of the result is used as is (warning binding.no_outputs). Every output key must be declared in the screen's data (binding.output_undeclared).
fallbackData used when the binding fails or times out.

Components read bound data with ${data.<key>}, for example "options": "${data.slots}".

In a static flow, only static and internal bindings on the entry screen are allowed. They are resolved once, when the flow is sent.

Built-in resolvers​

resolverparamsResult
contact_profilenone{ first_name, last_name, email, phone, language, birthday } of the recipient
custom_fieldsnone{ "<custom_field_id>": value } for all active, user-editable custom fields
custom_field_optionscustom_field_id{ options: [{ id, title }] }, the choices of a select custom field
listsonly_preference_center (bool), list_ids (string[]){ lists: [{ id, title }], subscribed_ids: [...] }, ready to use as CheckboxGroup options and init_value
tagsnone{ tags: [{ id, title }], contact_tag_ids: [...] }
catalog_productsproduct_retailer_ids (string[]), search (string), in_stock_only (bool), limit (default 20, max 200){ products: [{ id, title, description }], items: [...], count } from the product catalogue connected to the sending WhatsApp number

All values are strings or lists of strings, because WhatsApp Flow data is strictly typed. Missing values become "". Results of internal resolvers may be cached for up to a minute.

catalog_products​

Lists products from the WhatsApp product catalogue of the number that sends the flow. SendSeven uses its synced copy of the catalogue, so the data loads quickly; changes in Commerce Manager show up after the next catalogue sync. The sending channel is filled in automatically.

  • product_retailer_ids returns only these products, in this order. Without it, products are sorted by name.
  • search keeps products whose name or retailer id contains the text.
  • in_stock_only drops products that are not in stock.
  • products is ready to use as Dropdown, RadioButtonsGroup or CheckboxGroup options: id is the retailer id, title the product name (30 characters), description the formatted price.
  • items carries the details per product: id, name, description, price, price_label, currency, availability, url.
{
"id": "products",
"kind": "internal",
"screen_id": "PICK_PRODUCT",
"resolver": "catalog_products",
"params": { "in_stock_only": true, "limit": 10 },
"outputs": { "products": "products" },
"fallback": { "products": [] }
}

Components then use "data-source": "${data.products}". If the number has no catalogue, or no product matches, the list is empty. Declare products in the screen's data with at least one example item.

Example: a preference centre that shows all lists from the preference centre and pre-ticks the ones the contact already has:

{
"id": "prefs",
"kind": "internal",
"screen_id": "PREFERENCES",
"resolver": "lists",
"params": { "only_preference_center": true },
"outputs": { "lists": "lists", "subscribed": "subscribed_ids" }
}

What the managed endpoint does​

When a contact interacts with a dynamic flow, WhatsApp calls SendSeven, and SendSeven:

  1. Verifies and decrypts the request and checks the session's flow token.
  2. Resolves the screen:
    • On open (INIT), it renders the entry screen.
    • On back (BACK), it re-renders the named screen (if it has refresh_on_back).
    • On submit (data_exchange), it validates the submission, then renders the next screen. The default next screen is the first entry of the action's next_screens.
  3. Runs all bindings of the target screen in parallel. Built-in resolvers share a budget of about 900 ms; a webhook gets its own timeout (default 2500 ms). A binding that fails or is too slow uses its fallback.
  4. Filters the response to the keys declared in the target screen's data. Bound keys start empty, other keys keep the value carried from earlier screens or their example.
  5. Finishes the flow: when the submitted screen has no next screens, SendSeven completes the flow and WhatsApp closes it. The answers then arrive as described in Responses and webhooks.

The whole round trip must finish in under 10 seconds, so SendSeven stops waiting after about 9 seconds and answers with whatever data it has.

Server-side validation​

Every data_exchange submission is checked against the screen's inputs before anything else runs:

  • required (top-level inputs)
  • min_chars / max_chars and max_length
  • input_type email, number and phone
  • pattern
  • min_selected_items / max_selected_items

If something is wrong, the contact stays on the same screen and sees an error message, for example "Bitte prüfen: E-Mail". Your custom error_message on the input is used when you set one.

Friendly refusals​

The endpoint never shows the contact a technical error. Instead, it keeps the screen and shows a short message in the flow's language (English, German, French, Spanish, Portuguese and Italian are built in) when:

  • the workspace's plan no longer includes dynamic flows,
  • the flow's publication was paused or deprecated,
  • the flow receives more than 600 requests per minute, or the workspace more than 1200 per minute.

Data source API​

Webhook data sources are created once and then referenced by data_source_id from any number of flows. They need the Scale plan or higher.

MethodEndpointScope
GET/whatsapp-flow-data-sourceswhatsapp_flows:read
POST/whatsapp-flow-data-sourceswhatsapp_flows:write
GET/whatsapp-flow-data-sources/{id}whatsapp_flows:read
PATCH/whatsapp-flow-data-sources/{id}whatsapp_flows:write
POST/whatsapp-flow-data-sources/{id}/rotate-secretwhatsapp_flows:write
DELETE/whatsapp-flow-data-sources/{id}whatsapp_flows:write

Create​

curl -X POST https://api.sendseven.com/api/v1/whatsapp-flow-data-sources \
-H "Authorization: Bearer s7_api_a1b2c3d4e5f6789012345678abcdef00" \
-H "Content-Type: application/json" \
-d '{
"name": "Booking slots",
"source_type": "webhook",
"url": "https://booking.example.de/sendseven/flows",
"headers": { "X-Api-Key": "bk_live_…" },
"timeout_ms": 3000
}'
FieldNotes
nameRequired, up to 128 characters, unique in the workspace (409 name_taken).
source_typewebhook (default) or internal.
urlwebhook: required. Must be a public https:// URL.
headerswebhook: optional static headers. Allowed names: Authorization, X-Api-Key, Accept-Language. Values are single-line strings up to 1024 characters.
secretOptional signing secret, 16–256 characters. If you omit it, SendSeven generates one.
timeout_ms100–8000, default 2500.
is_activeDefault true. Inactive sources are skipped and their bindings use fallback.
resolver_keyinternal only: the built-in resolver name.

Response 201:

{
"id": "e4b1a0c2-…",
"name": "Booking slots",
"source_type": "webhook",
"resolver_key": null,
"url": "https://booking.example.de/sendseven/flows",
"has_secret": true,
"previous_secret_expires_at": null,
"timeout_ms": 3000,
"is_active": true,
"last_used_at": null,
"last_error": null,
"created_at": "2026-10-03T09:00:00Z",
"updated_at": "2026-10-03T09:00:00Z",
"signing_secret": "whfs_N3q…"
}
Secrets and header values are write-only

signing_secret is returned only once: on create (when SendSeven generated it) and on rotate-secret. Store it right away. The secret and all header values are write-only and never returned by any endpoint; responses only tell you that a secret exists (has_secret).

Update, rotate, delete​

  • PATCH accepts name, url, headers, secret, timeout_ms, is_active (and resolver_key for internal sources). Only the fields you send change. Because header values are never returned, send the complete set of headers you want whenever you change them.
  • POST /{id}/rotate-secret issues a new secret and returns it once as signing_secret. The old secret stays valid for 24 hours (previous_secret_expires_at). During that time every request is signed with both secrets, so you can deploy the new one without failed calls (see Rotating the secret). Only webhook sources have a secret (422 not_a_webhook).
  • DELETE returns { "success": true, "id": "…" }. Flows that still reference the source use their fallback data.

last_used_at and last_error show the most recent call and failure (for example timeout or http_status). See Custom webhook data source for the error codes.

Other errors on create or update: invalid_source_type, connector_unavailable, resolver_key_required, url_required, invalid_url, invalid_headers, invalid_secret (all 422), data_source_not_found (404).