Website forms
A Form section on a page collects what visitors type. This guide covers where those submissions
go: turning intake on, adding destinations, reading delivery outcomes, erasing values and receiving
the signed form.submitted webhook. To add the form itself to a page, see
Add a section.
Before you start
Section titled “Before you start”- You need the
site:infrapermission. The built-inadminrole has it, and so does a superadmin. Without it, the Integrations section is not shown. - The form intake Worker must be deployed, with a route for
<host>/v1/interactions/*on the site’s host. Without it, forms take no submissions. See Deploy changes. - Email destinations need mail set up for the installation (
RESEND_API_KEYandMAIL_FROM, or your own mail transport). See Add secrets for optional features.
Turn on submissions
Section titled “Turn on submissions”- Open Settings → Integrations. The Website forms card is below the outbound webhooks.
- Switch on Take submissions. The label changes to Forms take submissions.
- Set Keep what visitors send for (days): 1 to 365. The default is 30.
- Add destinations if you want site-wide operator delivery, then press Save form settings. Destinations are optional when you only want to store submissions.
While the switch is off, every form on the site answers that it is not taking submissions, and nothing is stored. Switching it off later does not cancel deliveries already queued.
Each site has its own settings. Forms on one site never send to another site’s destinations.
Add destinations
Section titled “Add destinations”A site sends each submission to at most 5 operator destinations. A form can override these defaults and send a separate acknowledgement email. Visitors never choose an operator destination, and nothing on the page names one.
- Add email adds an Email address. Enter one address.
- Add webhook adds a Webhook URL and a Signing secret. The URL must start with
https://and must not contain a username or password. The secret needs at least 16 characters (at most 256).
Each destination has its own Send to this destination switch: On or Paused. A paused destination gets no new submissions, and deliveries already queued for it end as Not delivered. Remove asks first. Submissions already stored are kept, and their queued deliveries to that destination end as Not delivered.
The secret is write-only. After saving, the field says A secret is stored. Type a new one to rotate it. Leave it empty to keep the stored secret. Each accepted submission keeps its destination URL, recipient and signing secret. Changes apply only to new submissions; retries keep the accepted settings. Keep the old secret available at your receiver while older deliveries remain queued.
With no site-wide destination, the card says No default destination. Submissions are still stored and listed. Form-specific destinations and acknowledgement emails can still deliver.
If mail is not set up, the card shows Email is not set up for this installation. Email deliveries then fail and are retried, like any unreachable destination, and end as Not delivered. After mail is set up, use Send again on each one.
Recent submissions
Section titled “Recent submissions”If recent submissions cannot be loaded, choose Try again in that section. This reloads the history only and keeps unsaved form settings. “No submissions yet” appears only after a successful read finds none.
The Recent submissions card lists the 50 newest receipts. The list itself does not show what a visitor typed; open a receipt to read retained values. Each receipt shows:
- the receipt id and when it arrived
- the page id, the language and when its values are erased (or Values erased)
- one line per delivery, with its channel, accepted recipient or webhook address, state and a short
detail such as
HTTP 503
Recipient addresses appear only while the submission values are retained. After expiry or erasure, the delivery line keeps its channel and state but withholds the target. Webhook addresses show only the origin and path, never query parameters or signing secrets. Legacy receipts without an accepted target snapshot show only the channel.
| State | Meaning |
|---|---|
| Waiting to send | Queued. The next delivery run sends it. |
| Will try again | The last attempt failed for a potentially temporary reason. Another attempt is scheduled. |
| Delivered | Email: the mail provider accepted it. Webhook: the receiver answered 2xx. |
| Not delivered | Stopped. The receiver refused it, every attempt failed, the destination was removed or paused, or the values were erased first. |
Deliveries run once a minute, at most 10 per site per run. A run claims each delivery just before it sends that one, and only one run can win a claim. A claim lasts 5 minutes from that moment. Each send gives up after 10 seconds, for email and for webhooks, and then counts as a failed attempt that is retried.
A run slowed down on one delivery never sends a later delivery that another run claimed or finished meanwhile. Before authorizing each send, it checks that the values have not expired or been erased. If a send is still unfinished when its claim ends, a later run can claim and send that delivery again; the late answer of the first run is ignored. A delivery can therefore be sent more than once: see Deduplicate.
Send again
Section titled “Send again”Send again appears on Will try again and Not delivered lines. It resets the attempt count
and queues the delivery now. The toast says It will be sent again within a minute. It reuses the
same delivery, so the receiver sees the same receipt and the same X-Deewan-Delivery value.
While a delivery run is sending that delivery, Send again is refused with
409 delivery_in_progress and the message This delivery is being sent right now. Check it again in
a few minutes. Nothing changes. Try again after the run finishes.
Sending again does not help when the values expired or were erased, or the destination was removed or paused. The delivery ends as Not delivered again. Retrying keeps the accepted recipient, URL, template and signing secret; it does not pick up later settings.
Erase values
Section titled “Erase values”Erase values deletes what the visitor sent, now, after a confirmation. The receipt stays, so a repeated send of the same submission is still recognised and nothing is stored twice. Deliveries not yet sent end as Not delivered.
File attachments
Section titled “File attachments”In the page editor, choose File upload as the field kind. Set File types to comma-separated
extensions or exact MIME types, for example .pdf, .png or application/pdf. Leave it empty to
allow the supported image and document formats. Wildcards such as image/* are not accepted.
Set Maximum files (1–5, default 1) and Maximum size per file (up to 5 MiB, default 5 MiB).
The whole submission may contain at most 5 files totalling 10 MiB. Required also applies to uploads.
Supported formats are PNG, JPEG, WebP, AVIF, GIF, PDF, DOCX, XLSX, PPTX, ODT, ODS, ODP, RTF, TXT and CSV. The intake checks the actual file bytes, not just the filename or browser’s reported type. Empty files, unsupported files and files over a configured limit are refused with a field error. The published form supports file selection and submission with or without JavaScript. Without JavaScript, a validation refusal reloads the page; the browser clears the file selection, so choose the files again before resending.
Open a retained receipt in Recent submissions to see each file’s name, type and size, then choose
Download. Downloads need the same site:infra permission as the submission details and use the
currently selected site. Visitor uploads stay in private submission storage; they are not public
Media library assets and do not get public URLs. The installation needs its private storage bucket
configured to accept attachments. Bind the same private R2 bucket as PRIVATE_MEDIA_BUCKET on the
intake Worker and the CMS Worker that handles downloads and scheduled cleanup. Generated intake
configuration copies the existing private bucket binding.
Attachments have the submission’s retention date. Erase values immediately removes their details and download access; expired submissions also refuse downloads before background cleanup runs. The retention job deletes their stored files and retries storage failures. The receipt stays. Already downloaded files and files copied by a recipient cannot be recalled.
Default operator emails list filenames and sizes, not file bytes. Webhooks include an optional
attachments map from field name to arrays of { id, name, type, size }; these are retained-file
references, not download URLs. Custom email template variables remain scalar field values.
Retention
Section titled “Retention”Each submission gets its erase date when it arrives: the arrival time plus the retention days set then. Changing the retention later affects only new submissions. At expiry, values and attachment downloads become inaccessible immediately, even before cleanup runs. The cleanup job runs once a minute for active and inactive sites. It removes retained values and attachment files, retries storage failures, and keeps the receipt. Disabling a site stops delivery, not retention cleanup.
What visitors meet
Section titled “What visitors meet”The intake checks each submission against the page’s published form, never a draft. A client may send 10 submissions per site in 10 minutes. A repeated send of the same submission is answered as already received without creating another receipt or delivery record. Use Preview on a selected form in the page editor to see each visitor state; see Add a section.
Email delivery
Section titled “Email delivery”The default email has the subject New form submission and one body line per field, in the form’s
order, as
label: value. A checkbox shows Yes or No and an empty answer shows —. A given consent adds
Consent: given. The last line names the receipt, the page, the language and the arrival time.
The mail provider has 10 seconds to accept an email. No answer, a network failure or a refusal is retried like a webhook failure, up to 5 attempts. A mail the provider accepted but whose answer was lost is sent again, so a mailbox can get the same submission twice. The default message includes the receipt id in its last line. Use it to identify duplicate messages. Custom templates contain the text you configured and do not automatically append receipt details.
Webhook contract
Section titled “Webhook contract”These destinations are separate from outbound webhooks. They are set only in Website forms, and
they send only form.submitted. The outbound webhook contract in
Outbound webhooks does not apply to them.
Request
Section titled “Request”Dee Wan sends POST to the destination URL:
| Header | Value |
|---|---|
Content-Type |
application/json |
X-Deewan-Event |
form.submitted |
X-Deewan-Delivery |
The delivery id: one submission to one destination |
X-Deewan-Signature |
sha256= and the lowercase hex HMAC-SHA256 of the exact body bytes, keyed with the destination’s signing secret |
The body (values are illustrative):
{ "event": "form.submitted", "receipt": "<receipt id>", "page": "<page id>", "locale": "en", "section": "<form section id>", "submitted_at": "2026-10-13T09:30:00.000Z", "labels": { "email": "Email", "subscribe": "Send me news" }, "consent": true, "attachments": { "brief": [ { "id": "<attachment id>", "name": "brief.pdf", "type": "application/pdf", "size": 1024 } ] }}receiptis the submission id, the same one Recent submissions shows.valuesmaps each field name to a string or a boolean.labelsmaps each field name to its label on the published form.submitted_atis when the intake stored the submission, as ISO-8601 UTC.
Answers and retries
Section titled “Answers and retries”- Any 2xx is Delivered.
- No answer within 10 seconds, a network failure, 408, 429 or any 5xx is retried.
- Every other status is final and ends as Not delivered. Redirects are not followed, so a 3xx is final too.
- At most 5 attempts in total. The wait starts at 1 minute and doubles, up to 1 hour, with random
jitter of 50–100 %. A valid
Retry-After(seconds or an HTTP date) can make the wait longer, up to 1 hour. - Each attempt uses the URL and signing secret accepted with that submission. Later edits apply only to new submissions. The destination’s current disabled or removed state still stops queued delivery.
Verify the signature
Section titled “Verify the signature”Verify the raw body bytes before parsing them. Re-serialized JSON does not match the signature. The
signature covers only the body; submitted_at is inside it. There is no separate timestamp header.
async function verifyFormWebhook(secret, rawBody, signatureHeader) { const hex = signatureHeader?.startsWith("sha256=") ? signatureHeader.slice(7) : ""; if (!/^[0-9a-f]{64}$/.test(hex)) return false; const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"], ); const signature = new Uint8Array( hex.match(/../g).map((pair) => parseInt(pair, 16)), ); return crypto.subtle.verify( "HMAC", key, signature, new TextEncoder().encode(rawBody), );}Deduplicate
Section titled “Deduplicate”Expect the same submission more than once. A lost 2xx is retried, Send again sends it again, and a send that outlives its 5-minute claim can be sent once more. Do not rely on exactly one request. Email can also repeat; the default message includes the receipt id in its last line.
- Deduplicate on
receipt. It is in the signed body and stays the same on every attempt, on Send again and for every destination of that submission. X-Deewan-Deliveryalso stays the same across attempts and Send again, but it differs per destination and is not signed. Use it to tell two destinations apart, not as the only key.- Store the
receiptand apply your application’s change in a single atomic operation, before you answer 2xx. Then a repeat does nothing.
Next step
Section titled “Next step”Add the form to a page in the page builder, or read the outbound webhook contract for content events.
Submission details and delivery options
Section titled “Submission details and delivery options”In Settings → Integrations → Website forms, open a receipt to read its retained values. Only operators with site:infra can read settings, receipt details or delivery history. Expired or erased values cannot be read or retried, even before scheduled cleanup runs. Erasing values also removes the queued delivery snapshots that contain them.
Delivery options lets you select an actual published page form and override its destinations. Forms without an override use the site defaults. An empty override sends no operator deliveries for that form; acknowledgement email is configured separately. Email destinations can use a plain-text Subject and Text template · {{field}}; Insert field inserts a declared form field. Templates accept {{field}} placeholders only, without executable expressions. Webhook secrets and destination configuration remain server-only and are excluded from public page data and site transfers.
Acknowledgement email sends a plain-text email to a chosen email field only when the published form has a consent control and the visitor checks it. It uses the same retained submission, queue, retries and erasure rules as operator delivery. It does not change the form’s success message.
A delivery keeps the recipient or webhook URL, template and signing secret accepted with the submission. Editing settings never redirects an old retry. Disabling or removing its destination stops queued delivery; disabling acknowledgement stops queued acknowledgements. Erasure and expiry are checked again immediately before starting transport. A provider request already in flight cannot be recalled.
An exact retry of an accepted request returns its existing receipt, even if forms were disabled, the published form changed or disappeared, the client rate limit was reached, or retained values were erased. It creates no new submission or delivery. A changed payload with the same key is refused. The final live check authorizes one transport attempt; erasure cannot atomically recall a provider request already authorized or in flight.
Unreleased / pre-MVP; no stable version
Source revision: fe23db481c422b40e317b92fc98116b4c0c26a87 (modified checkout)
Build date (UTC): 2026-10-18T09:22:34.630Z
Upgrade target: unavailable: modified checkout. Release notes