Webhooks
Custom payload templates
By default a generic webhook sends { "event": "submission.created", "submission": { ... } }, where submission holds the id, form_name, data (every field), ip_address, referer and created_at, plus a files array when the submission carried stored uploads.
On Pro ($5 per month) or Annual ($49 per year) you can replace that body with your own JSON from the webhook's Custom payload panel, which previews the result as you type. Put placeholders inside string values: { "email": "{{ email }}", "source": "Site: {{ submission.form_name }}" }. Field names match exactly first, then ignoring case, so {{ email }} also finds a field named Email.
A string that is exactly one placeholder keeps the value's real type: "{{ submission.data }}" becomes an object and a checkbox group stays an array. A placeholder mixed with other text is inserted as text. Missing values become null, or an empty string inside text. The built-in placeholders are submission.data, submission.id, submission.form_name, submission.form_id, submission.created_at, submission.ip_address, submission.referer and submission.files (download links that expire about 7 days after the submission).
The template has to be valid JSON (an object or an array) under 10,000 characters. It is parsed once and filled value by value, so nothing a visitor types can break the JSON your endpoint receives. If a saved template ever fails to parse, splitforms sends the standard payload instead of dropping the event. WhatsApp and Telegram hooks send plain text and don't take a template.
Custom or not, every request is signed: the X-Splitforms-Signature header is sha256= followed by the hex HMAC-SHA256 of the exact body, using that webhook's secret, so verify against the raw body. Each request times out after 8 seconds. A timeout, network error or 5xx gets one automatic retry about 2 seconds later; a 4xx is not retried.