Field Mapping
Every input in a builder doc can carry a mapping. When the contact completes the flow, SendSeven writes the answer to the contact. You do not need any code for this. Every answer, mapped or not, is also delivered in the completion webhook and to automations.
{
"type": "TextInput",
"name": "email",
"label": "E-Mail",
"input_type": "email",
"required": true,
"mapping": { "target": "contact_field", "field": "email" }
}
target | Writes to |
|---|---|
contact_field | A standard contact field |
custom_field | A custom field |
tag | Adds tags |
list_subscription | Subscribes to lists (with consent) |
variable_only | Nothing; the answer is only stored with the response |
Write-back rules
- Empty answers never clear a value. An untouched optional field leaves the contact as it was.
- Tags are only added, never removed.
- The phone number is never writable. It identifies the contact on WhatsApp.
- An e-mail address that belongs to another contact is not moved. The answer is kept in the response and reported as skipped (
owned_by_other_contact). - A value that cannot be converted (for example an invalid e-mail) is skipped with a reason; the rest is still written.
- Write-back is safe to repeat: subscriptions are idempotent, tags add-only, fields set-only.
The outcome of each write is reported in the writeback object.
Contact field
{ "target": "contact_field", "field": "first_name" }
field | Allowed components | Notes |
|---|---|---|
first_name, last_name | TextInput, TextArea, Dropdown, RadioButtonsGroup | Whitespace is collapsed. |
email | same | Lower-cased and checked. Use input_type: "email" (otherwise warning mapping.email_input_type). |
language | same | A language code such as de or de-AT. |
birthday | DatePicker, CalendarPicker, TextInput | ISO date (YYYY-MM-DD). |
Other components return mapping.wrong_component.
Custom field
{ "target": "custom_field", "custom_field_id": "3f1c…" }
Only active custom fields marked as editable by contact can be filled (mapping.custom_field_unknown, mapping.custom_field_not_editable). The value is converted to the field's type (text, number, date, boolean, select). For select fields, use the custom_field_options resolver to show the field's real choices.
Tag
{ "target": "tag", "tag_id": "b7d2…" }
tag_id: added when the answer is "truthy" (a tickedOptIn, a non-empty answer).option_tags: for selection inputs (Dropdown,RadioButtonsGroup,CheckboxGroup,ChipsSelector), a map from option id to tag id. Each selected option adds its tag.
{
"type": "RadioButtonsGroup",
"name": "budget",
"label": "Budget",
"options": [
{ "id": "small", "title": "Unter 5.000 €" },
{ "id": "large", "title": "Über 5.000 €" }
],
"mapping": { "target": "tag", "option_tags": { "large": "tag_hot_lead" } }
}
Codes: mapping.tag_missing (neither set), mapping.tag_unknown, mapping.option_unknown, mapping.wrong_component (option_tags on a non-selection input).
List subscription
List subscriptions need recorded consent. consent must be true and consent_text must contain the exact wording the contact agrees to. SendSeven stores a snapshot of this text, the flow version and the time with the subscription. Because the contact ticks the box themselves inside WhatsApp, no extra double opt-in message is sent.
Only an OptIn or a CheckboxGroup can subscribe (mapping.list_wrong_component).
OptIn: one list
{
"type": "OptIn",
"name": "newsletter",
"label": "Ja, ich möchte den Newsletter erhalten",
"mapping": {
"target": "list_subscription",
"list_id": "a9c4…",
"consent": true,
"consent_text": "Ja, ich möchte den Newsletter per WhatsApp erhalten. Abmeldung jederzeit möglich.",
"consent_version": "2026-10"
}
}
An OptIn needs a list_id (mapping.list_missing).
CheckboxGroup: several lists
Either map each option to a list with option_lists, or leave option_lists empty and use list ids as option ids, which is what the lists resolver produces:
{
"type": "CheckboxGroup",
"name": "topics",
"label": "Themen",
"options": "${data.lists}",
"init_value": "${data.subscribed}",
"mapping": {
"target": "list_subscription",
"mode": "sync",
"consent": true,
"consent_text": "Ich möchte zu den gewählten Themen Nachrichten per WhatsApp erhalten."
}
}
mode | Effect |
|---|---|
add (default) | Ticked lists are subscribed. Nothing is unsubscribed. |
sync | Ticked lists are subscribed, unticked lists that were offered are unsubscribed. Use it for a preference centre. CheckboxGroup only (mapping.sync_needs_checkbox). |
Safety rules:
- Only static and newsletter lists can be subscribed (
list_type_not_allowed). - Only lists that were actually shown to the contact count (
not_offered). A forged reply cannot subscribe to other lists. synconly unsubscribes lists that were offered and are visible in the preference centre.
With static options and no option_lists, the validator warns mapping.option_ids_as_lists to remind you that the option ids must be list ids.
Tags on completion
To tag every contact that completes the flow, regardless of the answers, use on_complete at the top level of the builder doc:
{ "on_complete": { "tag_ids": ["tag_lead_qualified"] } }
Variable only
{ "target": "variable_only" }
No mapping, or variable_only, stores the answer only with the response. It is still in the webhook, the sessions API and automation variables.
PhotoPicker and DocumentPicker are always variable only (mapping.media_not_mappable). Uploaded files are stored with the response and listed in writeback.media.
Sensitive fields
Add field names to a screen's sensitive list to hide them in WhatsApp's response summary on the contact's phone, for example a customer number or date of birth:
{ "id": "VERIFY", "title": "Identität", "sensitive": ["customer_number"], "components": [ … ] }
The sessions API and the whatsapp_flow.completed webhook mask these answers as •••• as well. Field mapping and automations inside SendSeven still receive the real values.