Webhooks
Send quiz data to your own endpoint, Zapier, Make or n8n. Setup, the default URL, the exact payload and event field, background delivery, retries and fixes.
A webhook sends a JSON message to a URL you choose whenever something happens in a quiz. You can use it to push leads and results into a spreadsheet, a CRM or any automation tool.
Webhooks are set up per quiz, and you can also set a default URL for all quizzes.
Set up a webhook
- Open Quiz Builder, then All Quizzes, and edit the quiz.
- Open the Integrations tab.
- On the Webhook card, turn on the toggle. The card shows a Connected badge.
- Paste your endpoint into Webhook URL, for example
https://hooks.example.com/quizably. - Select Send test to check the connection. A successful test shows Test delivered with HTTP 200. A failure shows Test failed.
- Save the quiz (Save or Next).
Use one URL for every quiz
If most of your quizzes send to the same place, set a default instead:
- Open Quiz Builder, then Integrations.
- On the Webhook card select Configure (or Edit webhook once one is set).
- Paste the address into Default webhook URL and select Save webhook. Leave it blank to turn the default off.
The address must be a valid http or https URL, otherwise it is not saved. The default applies to every quiz that has no webhook of its own: a quiz that has its own URL keeps it, a quiz whose webhook toggle you switched off stays off, and a quiz with the toggle on but a blank URL uses the default.
When a webhook is sent
Every message has the same shape. The event field tells you which kind it is.
event | When it is sent | lead | result, score, breakdown |
|---|---|---|---|
lead_captured | The visitor submits your lead form. With double opt-in, when the visitor clicks the confirmation link instead. | Filled in | Filled in only if the quiz was already scored, otherwise null |
submission_completed | The visitor finishes the quiz and it is scored | Filled in if the visitor gave their details during that visit, otherwise null | Filled in |
Things to know:
- If the quiz has no lead form (Show the form set to Off), only
submission_completedis sent, withleadset tonull. - If the quiz has a lead form and the visitor fills it in, both messages are sent. The
submission_completedmessage already contains the lead, so it is the complete record. - If the visitor skips an optional form, only
submission_completedis sent. - If the same email is submitted again for the same quiz, a new
lead_capturedmessage is sent. - A survey or a quiz with no result configured sends
resultasnull.
Payload
Every request is a POST with Content-Type: application/json.
{
"event": "submission_completed",
"quiz_uuid": "a1b2c3d4-0000-4000-8000-000000000000",
"quiz_title": "What's Your Coffee Personality?",
"quiz_type": "personality",
"submission_uuid": "e5f6a7b8-0000-4000-8000-000000000000",
"result": { "id": 12, "title": "The Flat White" },
"score": null,
"breakdown": { "tally": { "12": 4, "13": 1 }, "total_answers": 5 },
"lead": { "id": 5, "email": "ann@example.com", "name": "Ann", "phone": null, "double_optin_verified": null },
"timestamp": "2026-10-01T12:00:00+00:00"
}
| Field | Description |
|---|---|
event | lead_captured or submission_completed. |
quiz_uuid | The quiz’s unique ID (36 characters). |
quiz_title | The quiz title. |
quiz_type | personality, trivia, survey or poll. |
submission_uuid | Identifies one visit. The same value appears in both messages for that visit. |
result | { id, title } of the matched result, or null. The title is sent as stored and may contain HTML. |
score | The trivia score. null for other quiz types. |
breakdown | Details of the scoring. The shape depends on the quiz type, see below. |
lead | { id, email, name, phone, double_optin_verified } or null. Free forms collect name and email, so phone is null. |
timestamp | When the message was created, ISO 8601, UTC. |
lead.double_optin_verified is true when the lead has confirmed, false when the quiz uses double opt-in and the visitor has not clicked yet, and null when the quiz does not use double opt-in.
The breakdown object depends on the quiz type:
| Quiz type | Breakdown |
|---|---|
| Personality | { "tally": { "<result_id>": votes }, "total_answers": n } |
| Trivia | { "score": n, "correct_count": n, "total_questions": n } |
| Survey and poll | { "answered_questions": n, "total_questions": n } |
The payload does not include individual answers, typed text, ratings, UTM values or the consent flag.
Sample: form submitted, quiz not finished yet
This is a quiz with the form at Before quiz. The visitor has just submitted the form and has not answered anything.
{
"event": "lead_captured",
"quiz_uuid": "a1b2c3d4-0000-4000-8000-000000000000",
"quiz_title": "What's Your Coffee Personality?",
"quiz_type": "personality",
"submission_uuid": "e5f6a7b8-0000-4000-8000-000000000000",
"result": null,
"score": null,
"breakdown": null,
"lead": { "id": 5, "email": "ann@example.com", "name": "Ann", "phone": null, "double_optin_verified": null },
"timestamp": "2026-10-01T12:00:00+00:00"
}
Sample: quiz completed
{
"event": "submission_completed",
"quiz_uuid": "a1b2c3d4-0000-4000-8000-000000000000",
"quiz_title": "What's Your Coffee Personality?",
"quiz_type": "personality",
"submission_uuid": "e5f6a7b8-0000-4000-8000-000000000000",
"result": { "id": 12, "title": "The Flat White" },
"score": null,
"breakdown": { "tally": { "12": 4, "13": 1 }, "total_answers": 5 },
"lead": { "id": 5, "email": "ann@example.com", "name": "Ann", "phone": null, "double_optin_verified": null },
"timestamp": "2026-10-01T12:00:04+00:00"
}
The test message from Send test is different. It contains only event (set to test), quiz_uuid, quiz_title and timestamp.
One record per visitor
The simplest setup is to act only on submission_completed and ignore lead_captured. That message carries the lead and the result together, and it is sent once per finished visit.
In an automation tool such as Zapier, Make or n8n:
- Create a webhook trigger and paste its URL into Webhook URL.
- Add a filter that continues only when
eventequalssubmission_completed. - If
leadis not empty, add the email and name to your list or sheet together withresult.titleandscore.
Use lead_captured instead when you want leads from people who never finish the quiz. With a form before the quiz, that message arrives as soon as the form is sent. When you use both events, match them on submission_uuid with a “find or create” step, because retries can deliver messages out of order.
Note: With double opt-in on,
submission_completedarrives before the visitor confirms and showsdouble_optin_verifiedasfalse. Subscribe people onlead_captured, or check that field first.
Delivery, timeouts and retries
Webhooks are sent in the background. When a quiz event happens, Quizably queues the delivery with WordPress cron and the visitor moves on at once, so a slow endpoint does not hold up the result screen. Each delivery waits up to 8 seconds for your endpoint to answer.
Note: Delivery depends on WP-Cron. If your site sets
DISABLE_WP_CRONtotrue, queued webhooks are only sent when a real server cron job callswp-cron.php. Without one, nothing is delivered.
The response code decides what happens next:
| Response | What Quizably does |
|---|---|
Any 2xx | Counts as delivered. |
5xx, 429, a timeout or a connection error | Retries. |
Any other 4xx | Treats it as failed and does not retry. |
Retries run up to 3 times, about 1, 2 and 4 minutes after the previous attempt (roughly 7 minutes in total, 4 attempts at most). This matches the help text under Webhook URL. Retries also use WordPress cron, so they need the site to receive traffic or have a working cron.
There is no delivery log and no failure alert. The Integration sync area in a lead’s detail panel stays empty. If you want to see what was sent, point the webhook at a request catcher while you test.
Developers can switch off the background queue and send during the visitor’s request with add_filter( 'quizably_integration_async', '__return_false' );. See Hooks and filters.
Testing with a request catcher
- Create a temporary URL with any public request catcher service.
- Paste it into Webhook URL and select Send test.
- Check that the catcher received the test message.
- Take the quiz yourself and watch for the real messages: one if there is no form, two if there is.
- Replace the URL with your real endpoint and turn the test URL off.
Security
- Use an
https://URL. Certificates are verified, so a self-signed certificate fails. - Treat the URL as a secret. Anyone who has it can send fake data to your automation. Use a long, unguessable path if your tool allows it.
- The free webhook does not sign requests. Do not assume a request is genuine because of its payload alone.
- The payload contains personal data such as email and name. Only send it to services you trust.
Common problems
| Problem | Likely cause and fix |
|---|---|
| Test failed | Check the URL for typos and that the endpoint accepts POST with JSON. |
| Nothing arrives from real visits | The toggle is off, the quiz is a draft, or the URL points to localhost or a private network address. Those are refused. Use a public URL. |
| Lead arrives but no result | That is the lead_captured message, sent before the quiz was scored. The result arrives in the submission_completed message. |
Result arrives but lead is empty | The visitor skipped the form, or the quiz has none. |
Nothing arrives and the site sets DISABLE_WP_CRON | Queued webhooks need a real cron job to call wp-cron.php. |
No lead_captured message with double opt-in on | It is sent only after the visitor clicks the confirmation link. |
| Only one message per visitor | The form is Off, the visitor skipped it, or the visitor has not confirmed yet. |
Related
Last updated October 4, 2026.