Each form can POST its submissions to an external URL - connect a form to your CRM, or to a catch-hook in Zapier, Make, or n8n, and every submission shows up there the moment it arrives.
Form webhooks are a paid plan feature.

Each delivery is a JSON POST with one of these events:
submission.created - fires on every submission, including pending ones. This is the CRM moment - the lead exists whether or not you approve the task.submission.approved - fires on approval, including the created task's id and key.submission.declined - fires on decline.test - fired by the Send test event button.Every request is a POST with Content-Type: application/json, a User-Agent of t0ggles-webhook, and two custom headers:
x-t0ggles-event - the event name.x-t0ggles-signature - HMAC-SHA256 hex signature of the request body (see Verifying Deliveries).A submission.approved delivery looks like this:
{"event": "submission.approved","createdAt": "2026-08-14T10:30:00.000Z","form": {"id": "C71j6nT5SIsu9R5TEkZ1","title": "Bug Report"},"submission": {"id": "Z11rRJYBEKnofHzzkBYg","createdAt": "2026-08-14T10:24:03.000Z","status": "created","submitterUserId": null,"submitterEmail": "jane@example.com","emailVerified": true,"answers": {"f_title": "App crashes on login","f_severity": "tag_8Fj2k1","f_screenshots": [{"url": "https://cdn.t0ggles.com/...","name": "crash.png","type": "image/png","size": 48211}]},"fields": [{"id": "f_title","label": "What happened?","kind": "text","value": "App crashes on login"},{ "id": "f_severity", "label": "Severity", "kind": "choice", "value": "Critical" },{ "id": "f_screenshots", "label": "Screenshots", "kind": "file", "value": "crash.png" }]},"task": {"id": "aB3xY9...","key": "MOBILE_APP-6","title": "App crashes on login"}}
Two views of the same answers:
answers is the raw record, keyed by field id. Values are strings, numbers, booleans, arrays for multi-choice, or file objects. Options of a mapped choice field submit the underlying entity id - stable for machine matching.fields is the display-friendly list in form order - one label/value pair per answered field, with choice ids resolved to their labels. Use it when you just want readable text in your CRM.Notes per event: on submission.created the status is pending (or created when the form doesn't require approval), and task is null unless the task was created immediately. On submission.declined the status is declined. The test event sends "submission": null, "task": null. For members forms submitterUserId is set; for anonymous public submissions both submitter fields are null.
When you first set a URL, t0ggles generates a signing secret (shown next to the URL with a copy button). Every request's x-t0ggles-signature header is the HMAC-SHA256 of the request body, computed with that secret and hex-encoded. Recompute it on your side and compare - if it matches, the delivery came from t0ggles and wasn't tampered with.
Sign the raw request body exactly as received - parsing and re-serializing the JSON can reorder or reformat it and break the signature. A minimal Node.js/Express receiver:
import { createHmac, timingSafeEqual } from 'node:crypto';import express from 'express';const app = express();const SECRET = process.env.T0GGLES_WEBHOOK_SECRET;// express.raw keeps the body as the exact bytes t0ggles signedapp.post('/hooks/t0ggles', express.raw({ type: 'application/json' }), (req, res) => {const signature = Buffer.from(req.get('x-t0ggles-signature') || '', 'utf8');const expected = Buffer.from(createHmac('sha256', SECRET).update(req.body).digest('hex'), 'utf8');if (signature.length !== expected.length || !timingSafeEqual(signature, expected)) {return res.status(401).end();}const payload = JSON.parse(req.body);if (payload.event === 'submission.created') {// e.g. create a lead in your CRM from payload.submission.fields}res.status(200).end();});
Respond with a 2xx quickly - deliveries time out after 10 seconds. Network errors and 5xx responses are retried once; 4xx responses are not. The last delivery result is always visible in the form builder's Webhook section.