# OllieLeads integration reference > How to send a website form's leads to OllieLeads: the endpoint, content types, headers, responses, reserved fields, server-to-server forwarding, test leads, limits and where data is stored. Checked against the live endpoint, October 2026. For the overview, see https://ollieleads.com/llms.txt. OllieLeads is a form backend, not a form builder. The site keeps its own form, markup, design and submit logic; OllieLeads stores every lead the form sends, catches people who leave halfway (with the SDK, in the pilot), and notifies the owner. Each form has an ID like frm_8kq2abcd, shown on the owner's Connect screen. ## What works today, and what is coming Works today: - The form endpoint, for plain HTML forms, browser fetch and server-to-server forwarding. - Server keys for forwarding from the site's own backend. - Test leads, from the public test key or the "Send a test lead" button on the Connect screen. - Notifications in Slack and Discord. - The app at https://app.ollieleads.com: leads, brands, forms, the Connect flow. Coming in the pilot (do not build against these yet): - The JavaScript SDK on npm (early saving of contact fields), remote MCP, the REST API, signed webhooks, `npx ollieleads connect`, email alerts, the MailBluster connector, file uploads. ## The endpoint POST https://ollieleads.com/f/{formId} - Content types: application/x-www-form-urlencoded, multipart/form-data and application/json. - JSON bodies are a flat object of fields. Arrays become lists; nested objects are stored as JSON text. - Repeated form fields with the same name become a list. - Files are not stored: a file field's value becomes "[file not stored]". - The body may be at most 64 KB. - GET returns 405 with a JSON explanation. ## Which way to connect Pick the one that matches how the form submits today, and keep everything the site already does on submit. A. A plain HTML form with no submit handler: set method="post" and action="https://ollieleads.com/f/{formId}". Optionally add a hidden field _next with the URL of the site's own thank-you page. If the site sends a Content-Security-Policy, add https://ollieleads.com to form-action. B. A submit handler in the browser: also POST the fields as JSON to the endpoint with "Accept: application/json". Use a timeout of about 3 seconds and never let it block or break the form. If the site sends a Content-Security-Policy, add https://ollieleads.com to connect-src. C. A server handler (a Worker, an API route, a server action, a backend): after the site's own checks pass, POST the fields as JSON to the endpoint from the server, with a server key. This keeps the site's validation, emails, thank-you page and language handling exactly as they are. See "Server-to-server forwarding" below. ## Headers - Accept: application/json. Ask for a JSON response instead of the thank-you page. - X-Ollie-Key: a key. "ol_test_connect" (or any key starting ol_test_) marks the lead as a test. A server key (ol_live_ followed by 32 characters) marks a server-to-server request. - X-Ollie-Idempotency-Key: up to 128 characters, one per submission. Sending the same key again never creates or counts a second lead, so retries are safe. Without one, identical posts to the same form within 10 minutes are treated as one. - Only with a valid server key: X-Ollie-Page-Url (the page the visitor was on), X-Ollie-User-Agent (the visitor's browser) and X-Ollie-Visitor-Ip (the visitor's IP, stored only as a daily-rotating hash). Without a valid key these headers are ignored. ## Reserved fields Fields whose names start with an underscore are control fields. They are read, never stored: - _next: where to send the visitor after a browser post. Only URLs on the brand's own site are followed; anything else is ignored. - _hp: a honeypot. Leave it empty and hide it from people; a filled one marks the post as spam, which is never stored as a lead or counted. - _key: the same as X-Ollie-Key, for plain HTML forms. Only ever put the public test key here, never a server key. - _idem: the same as X-Ollie-Idempotency-Key. - _sid and _ssec: used by the SDK. Values that must never be kept are replaced by "[not stored]" while the field name stays: fields named like a password, card number, CVC or CVV, security code, PIN, IBAN or social security number, and any value that looks like a card number. ## Responses A plain browser post (no Accept: application/json): - 200 with a thank-you page, or 303 to _next when it points at the site. - If OllieLeads cannot store the lead (very rare), a page shows the visitor what they typed so nothing is lost. With Accept: application/json, or a JSON body: - 200 {"ok": true, "ingest_id": "...", "mode": "live" | "test"}. With a valid server key it also has "via": "server". - A lead can come back with a "warning" object, for example {"error": "invalid_key", "message": "...", "fix": "..."}. The lead was still saved. Errors are JSON: {"ok": false, "error": "...", "message": "...", "fix": "...", "docs": "https://ollieleads.com/llms.txt"} - 400 unreadable_body: the body could not be parsed. - 403 origin_not_allowed: a browser post from a site that is not the brand's. The lead is not kept. To allow another site for a brand, email hello@ollieleads.com (the setting is coming to the app). - 404 form_not_found: the ID is not a form ID. - 405 method_not_allowed: use POST. - 413 payload_too_large: over 64 KB. - 429 rate_limited: too many posts; wait a minute and retry with the same idempotency key. - 503 temporarily_unavailable: retry with the same idempotency key; it will not be counted twice. A well-formed form ID that does not exist is accepted with 200 and then discarded; check the ID on the Connect screen. ## Origins - Browser posts are accepted from the brand's site: https://{domain} and https://www.{domain}. - Posts with a test key are also accepted from local development at http://localhost:3000, :5173, :4321 and :8080. - Requests without an Origin header (servers, curl, scripts) are accepted. - A browser post from any other site gets 403 origin_not_allowed. ## Server-to-server forwarding Use this when the form already posts to the site's own server code. 1. On the Connect screen in the app (Your server tab), the owner creates a server key. It is shown once; only its hash is stored. The owner adds it to the site as a secret, for example OLLIE_SERVER_KEY. 2. After the site's own checks pass, the server POSTs the fields as JSON to the endpoint with X-Ollie-Key set to the server key, plus X-Ollie-Page-Url, X-Ollie-User-Agent, X-Ollie-Visitor-Ip and X-Ollie-Idempotency-Key. 3. Use a timeout of about 3 seconds and never fail the visitor's request because of OllieLeads. On Cloudflare Workers, send it inside ctx.waitUntil. What a valid server key changes: - The browser origin check does not apply. - The forwarded visitor details are trusted and stored. - Rate limiting is per key (600 a minute) instead of per visitor IP. - Leads are marked as coming from the server, and the app shows when the key was last used. What can go wrong: - An unknown or revoked key: the lead is still saved, without the server-only details, and the JSON has "warning": {"error": "invalid_key"}. - A server key sent from a browser (a request with an Origin header) is ignored, with "warning": {"error": "server_key_in_browser"}. Revoke that key and make a new one. Cloudflare Worker example: ctx.waitUntil( fetch('https://ollieleads.com/f/frm_xxxxxxxx', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json', 'x-ollie-key': env.OLLIE_SERVER_KEY, 'x-ollie-page-url': request.headers.get('referer') ?? '', 'x-ollie-user-agent': request.headers.get('user-agent') ?? '', 'x-ollie-visitor-ip': request.headers.get('cf-connecting-ip') ?? '', 'x-ollie-idempotency-key': crypto.randomUUID() }, body: JSON.stringify(fields), signal: AbortSignal.timeout(3000) }).catch(() => {}) ); Node, Next.js route handlers, SvelteKit and Remix work the same way with process.env.OLLIE_SERVER_KEY and the visitor IP from x-forwarded-for. ## Test leads - Send any post with X-Ollie-Key: ol_test_connect (or the hidden field _key=ol_test_connect). - Or press "Send a test lead" on the Connect screen. - Test leads are tagged [TEST], go through the whole loop including notifications (marked as tests), and never count toward the plan. - Example: curl -X POST https://ollieleads.com/f/frm_xxxxxxxx -H "X-Ollie-Key: ol_test_connect" -H "Accept: application/json" -d "name=Test Visitor" -d "email=test@example.com" ## Limits - Browser posts: 30 a minute per visitor per form. Server keys: 600 a minute per key. - 64 KB per post. ## What is stored - Every named field as sent, except reserved fields and values that must never be kept (see above). - Where it came from: the page, the referring origin, UTM tags, the browser's user agent, the country, and a daily-rotating hash of the IP. The IP itself is never stored. - Each lead counts once. Spam and test leads never count. ## Notifications - Slack and Discord, usually within seconds. - Sales, Quote and Contact forms alert for every lead. Feedback, Support and Newsletter forms are saved without an alert for each one; a daily summary is coming. - Email alerts are coming in the pilot. ## Data and privacy - Leads are stored in the EU, in Frankfurt. - Cloudflare runs the endpoint and keeps an archive copy of every post in its EU jurisdiction; Neon runs the database on AWS in Frankfurt. - Each customer and each brand is kept apart by row-level security in the database. - Deletion requests: privacy@ollieleads.com. - The privacy policy, terms and data processing agreement are at https://ollieleads.com/privacy, https://ollieleads.com/terms and https://ollieleads.com/dpa. They are drafts under legal review; the data processing agreement is available on request.