Webhooks: every response, signed, to your system
Paste a URL in the form's settings and every response arrives there as a JSON POST, signed with a secret only you can see. It works with Zapier, Make, n8n or your own server.
What arrives
{
"event": "response.created",
"delivery": "5f0c2d1e-8a47-4d8e-9c1b-2f6a7e3b9d10",
"form_id": 123,
"form_title": "Cena de equipo",
"slug": "cena-de-equipo-1a2b3c",
"answers": {
"donde": "Sidrería",
"comentario": "Mejor el viernes"
},
"response_id": 456,
"ts": "2026-09-26T19:42:07Z"
}
answers uses the question ids of your form. event is response.created
(or ping for the test button). delivery is unique per delivery: if the same one arrives
twice, process it once.
Headers
X-AIM-Timestamp: when it was sent, in Unix seconds.X-AIM-Signature:sha256=followed by the HMAC-SHA256, in hex, of<timestamp>.<body>with your form's secret.X-AIM-EventandX-AIM-Delivery: the same as in the body.
Check the signature
Use the body exactly as it arrived (the raw bytes, before parsing the JSON), and reject anything older than 5 minutes: that way a delivery someone captured can't be replayed.
Python
import hashlib, hmac, time
def signature_is_valid(secret, headers, body):
ts = headers.get("X-AIM-Timestamp", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
return False
expected = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, headers.get("X-AIM-Signature", ""))
Node.js
const crypto = require("crypto");
function signatureIsValid(secret, headers, body) { // body: a Buffer, exactly as it arrived
const ts = headers["x-aim-timestamp"] || "";
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(ts + ".").update(body).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(headers["x-aim-signature"] || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
If your system doesn't answer
Anything other than a 2xx reply within 8 seconds counts as a failure, and we retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours: almost a day. The queue lives in the database, so a server restart doesn't lose it. The form's settings show the latest deliveries and let you send a test.
Anonymous answers and secret ballots
So the timing doesn't give anyone away, the answers of an anonymous form or a secret ballot go out once a day, just after midnight (UTC), with the date and no time, and without a response number that would reveal the order.
Your data, and ours
The body of each delivery is kept only until it's delivered (or, after the last retry, given up on) and the delivery log is deleted after 7 days. We don't send to private addresses (your local network, loopback) and we don't follow redirects: if your URL redirects, the delivery counts as failed. If you think your secret has leaked, change it from the form's settings: the old one stops working straight away.