Webhooks
Send responses to your own system as signed JSON, with retries.
Send every response, or every paid order, to your own system: a CRM, a spreadsheet script, a kitchen display. Mikxx POSTs it as JSON to your URL, signed so you can trust it.
Set it up
- Open the form's page → Automations → Send to a webhook.
- Name it, choose When (a response arrives, or an order is paid), and enter your URL.
- Click Add automation. Copy the signing secret it shows (
whsec_…). - Click Send test to check your endpoint answers.

What Mikxx sends
{
"event": "submission.created",
"test": false,
"form": { "id": "…", "name": "Workshop sign-up" },
"submission": {
"id": "…",
"created_at": "2026-10-27T10:30:00Z",
"answers": [
{ "id": "name", "question": "Your name", "answer": "Asha" },
{ "id": "days", "question": "Days", "answer": "Sat, Sun" }
],
"data": { "name": "Asha", "days": ["Sat", "Sun"] }
},
"respondent": { "email": "[email protected]" }
}
event is submission.created or submission.paid. answers is easy to read; data has the
raw values keyed by question id. respondent.email is the answer to the form's first email
question, when there is one that looks like an address; otherwise respondent is left out.
Headers
| Header | Value |
|---|---|
X-Mikxx-Event | The event, as in the body. |
X-Mikxx-Timestamp | When it was sent, in Unix seconds. |
X-Mikxx-Signature | sha256= and the HMAC-SHA256 of timestamp.body, keyed with your signing secret. |
Check the signature
Recompute the signature from the timestamp and the raw body, and compare. Reject old timestamps to stop replays.
import crypto from "node:crypto";
function isFromMikxx(rawBody, headers, secret) {
const ts = headers["x-mikxx-timestamp"];
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const given = headers["x-mikxx-signature"] ?? "";
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
return fresh && given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}
import hashlib, hmac, time
def is_from_mikxx(raw_body: bytes, headers, secret: str) -> bool:
ts = headers["X-Mikxx-Timestamp"]
expected = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(ts)) < 300
return fresh and hmac.compare_digest(headers.get("X-Mikxx-Signature", ""), expected)
Delivery
- Answer with any
2xxwithin 10 seconds to accept it. Anything else is a failure. - Failures are retried after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours, then marked failed. History shows each try's status code or error; Retry starts again.
- A send can arrive more than once. Treat
submission.idpluseventas unique and ignore repeats.
Rules for URLs
https://only, with no user name or password in the URL.- Public addresses only: private, local and internal network addresses are refused.
- Redirects aren't followed.