A Cloudflare Workers form handler for Anticode site functions
A site function lets your own code decide what happens when a visitor sends a form on an Anticode site, and a Cloudflare Worker is a cheap, fast place to run that code. Anticode calls the Worker with a signed JSON request while the visitor waits, and whatever title, message and lines the Worker returns are shown on the thank-you page. The Worker must sit on your own domain, not a workers.dev address.
Site functions in one paragraph
Published Anticode pages run no scripts, so logic can't live in the page. A site function moves it to the server: you attach a function to one of the site's forms (booking, contact, newsletter) or to a custom form that posts to /api/fn/<name> with up to 30 fields. When a visitor submits, Anticode's server signs a request, calls your HTTPS endpoint, and renders the reply as plain text in the site's own design. Anticode never runs your code. A Cloudflare Workers form handler is simply the endpoint on the other side.
Deploy the Worker on your own domain
Anticode refuses to call *.workers.dev hosts, along with IP addresses, private names and anything not on HTTPS port 443. So after deploying, attach the Worker to a hostname on a zone you have on Cloudflare. In the dashboard that's Workers & Pages, your Worker, then Settings › Domains & Routes › Add › Custom Domain. Cloudflare's Custom Domains guide notes that it creates the DNS record and certificate for you. Something like forms.yourdomain.com works well.
What Anticode sends
A POST with content-type: application/json. The body holds type (always anticode.function), version (1), a run id, the function name, the site, the form (booking, contact, subscribe or custom), the visitor's data, a test flag that's true when you press Test in Anticode, and sentAt. The header that matters is:
anticode-signature: t=1761300000,v1=5f2c…tis the Unix time in seconds when Anticode signed the call.v1is the lowercase hex HMAC-SHA256 of the stringt, a full stop, then the raw request body, keyed with the function's signing secret.
Verify the signature
Workers support HMAC through the Web Crypto API, plus a timing-safe comparison, as Cloudflare's Web Crypto page lists. The order of operations matters more than the code:
- Store the signing secret with
npx wrangler secret put ANTICODE_SECRETand read it asenv.ANTICODE_SECRET. Cloudflare's secrets guide covers this. Anticode shows the secret once, when you create the function or press New secret. - Read the body as text with
await request.text()before parsing. Re-serialising parsed JSON changes the bytes and the signature won't match. - Split the header on the comma into
tandv1. Reject the request iftis more than about five minutes away from the current time, which stops old requests being replayed. - Import the secret as a raw HMAC key with SHA-256, sign
t + "." + body, and hex-encode the result. - Compare it to
v1withcrypto.subtle.timingSafeEqualon equal-length byte arrays. If they differ, return 401. - Only now
JSON.parsethe body and do your work.
Reply with what the visitor should see
Answer 200 with JSON. Every key is optional:
| Key | Effect | Limit |
|---|---|---|
ok | false refuses the submission: it isn't stored and the visitor sees your title and message | true or false |
title | The thank-you page heading | 80 characters |
message | A sentence or two under it | 600 characters |
lines | A list of { "label", "value" } pairs, such as a quote breakdown | 12 shown; label 60, value 200 |
The reply is treated as text, never HTML: control characters are removed, lengths are capped and unknown keys are dropped, and the whole response must be under 16 KB. A <b> in your message appears as those characters.
Timeouts and failure
Each function has a timeout between 1 and 8 seconds, 4 by default. A timeout, a non-2xx status, an unreachable host or a reply that doesn't fit the shape counts as a failure, and the visitor never sees your Worker's own error text. You choose what failure means: accept stores the submission and shows the normal thank-you (the default), refuse tells the visitor to try again and stores nothing. Use refuse only for checks that must pass, such as an availability lookup. Workers start quickly, but any upstream API you call has to fit inside the same deadline.
Limits
- Nothing runs as the visitor types; the Worker only sees the finished form.
- The page can't run your JavaScript or embed your Worker's HTML.
- For arithmetic alone, such as a quote total, a formula function runs on Anticode's server with nothing to host.
- For sending submissions on after they're stored, the plain webhook connector is simpler and needs no reply.
Questions people ask
Why can't I use my workers.dev address?
Anticode blocks workers.dev and other Cloudflare internal hosts as destinations. A Custom Domain on your own zone takes a minute to add.
What happens if I rotate the secret?
New secret replaces the old one immediately and shows the new value once. Update the Worker's secret straight after, or signatures will fail.
Can the Worker read a Google Sheet or database?
Yes. It's your code with your credentials, as long as it answers within the function's timeout.
Sources
- Cloudflare Workers: Custom Domains, checked
- Cloudflare Workers: Web Crypto, checked
- Cloudflare Workers: Secrets, checked