This page traces a form submission from the initial HTTP request through delivery to its final destination.
When a client submits a form, the following steps execute in the Cloudflare Worker:
Parse the public key. The Worker extracts the publicKey from the URL path /v1/f/:publicKey.
KV lookup. The Worker reads the form configuration from the FORM_KV namespace using the public key. If the key is not found or the form is inactive, it returns 404.
Origin check. The Worker reads the Origin header and checks it against the form's allowedOrigins list. An empty list means all origins are allowed. A mismatch returns 403.
Payload size check. If the Content-Length header exceeds 128 KB (131,072 bytes), the Worker returns 413.
Parse JSON body. The Worker parses the request body as JSON. If parsing fails, it returns 400.
Captcha verification (optional). If the form has verifyCaptcha enabled, the Worker reads the x-captcha-token header and verifies it against the captcha provider. A missing or invalid token returns 400.
Build the SubmissionEnvelope. The Worker constructs the envelope containing the submission ID, form metadata, the parsed payload, and request metadata.
Enqueue messages. For each active destination, the Worker enqueues a separate queue message containing the envelope and the destination config.
Return 202 Accepted. The Worker responds immediately with the submission ID and the count of queued destinations, along with CORS headers if an origin was present.
Every queue message wraps the form data in a SubmissionEnvelope:
| Field | Description |
|---|---|
submissionId |
UUID generated per submission |
formId |
The internal form ID from D1 |
formName |
Human-readable form name |
payload |
The raw JSON body submitted by the client |
metadata.origin |
The Origin header value |
metadata.ip |
The client IP from CF-Connecting-IP |
metadata.userAgent |
The User-Agent header value |
metadata.referer |
The Referer header value |
metadata.submittedAt |
ISO 8601 timestamp of when the submission was received |
Each queue message pairs a single destination with the full envelope:
If a form has 3 destinations, 3 separate queue messages are produced from a single submission.
The queue consumer processes messages in batches (max 10 per batch):
Iterate messages. The consumer loops through each message in the batch.
Dispatch to connector. Each message is passed to dispatchToDestination, which routes to the appropriate connector based on destination.type:
webhook -- sends an HTTP request to the configured URLsmtp -- sends an email via raw SMTP socketsgoogle_sheets_webhook -- sends an HTTP request to a Google Apps Script endpointHandle result. The connector returns a DeliveryResult:
Acknowledge or retry.
result.ok is true, the message is acknowledged (message.ack()).result.ok is false or an exception is thrown, the message is retried (message.retry()).Dead letter queue. After 5 failed retries, the message is moved to formdata-delivery-dlq. It will not be retried further.
The dispatchToDestination function routes based on destination type:
| Type | Connector | What It Does |
|---|---|---|
webhook |
sendWebhook |
HTTP POST/PUT/PATCH to the configured URL with the full envelope as the JSON body. Supports custom headers. |
smtp |
sendSmtp |
Connects to the SMTP server via raw TCP sockets (cloudflare:sockets). Supports STARTTLS, plain TLS, and unencrypted connections. Sends a formatted email with the submission data. |
google_sheets_webhook |
sendGoogleSheetsWebhook |
HTTP POST/PUT to a Google Apps Script web app URL. Optionally includes an authorization header. |
Here is the complete flow for a form with a webhook and an email destination:
POST /v1/f/pk_abc123 with a JSON body.pk_abc123 in KV, finds an active form with 2 destinations.SubmissionEnvelope.202 Accepted with queuedDestinations: 2.200 OK, and acknowledges.The 202 response means the submission was accepted and queued for delivery. It does not guarantee delivery succeeded. If a destination is down, the queue retries up to 5 times with a 30-second delay between attempts.