Anticode Open builder

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.

Updated · By the Anticode team

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…
  • t is the Unix time in seconds when Anticode signed the call.
  • v1 is the lowercase hex HMAC-SHA256 of the string t, 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:

  1. Store the signing secret with npx wrangler secret put ANTICODE_SECRET and read it as env.ANTICODE_SECRET. Cloudflare's secrets guide covers this. Anticode shows the secret once, when you create the function or press New secret.
  2. Read the body as text with await request.text() before parsing. Re-serialising parsed JSON changes the bytes and the signature won't match.
  3. Split the header on the comma into t and v1. Reject the request if t is more than about five minutes away from the current time, which stops old requests being replayed.
  4. Import the secret as a raw HMAC key with SHA-256, sign t + "." + body, and hex-encode the result.
  5. Compare it to v1 with crypto.subtle.timingSafeEqual on equal-length byte arrays. If they differ, return 401.
  6. Only now JSON.parse the body and do your work.

Reply with what the visitor should see

Answer 200 with JSON. Every key is optional:

The reply Anticode reads
KeyEffectLimit
okfalse refuses the submission: it isn't stored and the visitor sees your title and messagetrue or false
titleThe thank-you page heading80 characters
messageA sentence or two under it600 characters
linesA list of { "label", "value" } pairs, such as a quote breakdown12 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

  1. Cloudflare Workers: Custom Domains, checked
  2. Cloudflare Workers: Web Crypto, checked
  3. Cloudflare Workers: Secrets, checked