Skip to content
AIM FORM

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

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.