Developers

Webhooks

Each new response posted to your server, signed and retried.

A webhook posts each new response to a web address of yours as it arrives — to a CRM, a Slack bot, a spreadsheet script or your own backend.

Adding a webhook

  1. Open the form and go to its Share tab.
  2. Choose Add a webhook: a name, the address to post to (https:// recommended), and a secret of your own, used to sign every delivery.
  3. Choose the events and save. Each webhook belongs to the one form it was added on.

Webhooks need the Admin or Owner role.

Events

EventWhen
submission.createdA new response is saved. Sent straight away.
submission.updatedA response is changed. Queued, and sent with the next round of deliveries (see retries).

The request

Each delivery is a POST with a JSON body:

Headers
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: datamold/1.0
X-Form-Event: submission.created
X-Form-Delivery: 01J…
X-Form-Signature: sha256=5d41402abc4b2a76b9719d911017c592…
Body
{
  "event": "submission.created",
  "at": 1791965028499,
  "form": { "id": "01J…", "name": "Event registration", "slug": "k7q2m9xa3b" },
  "submission": {
    "id": "01J…",
    "code": "K7Q2M",
    "status": "complete",
    "stage": "New",
    "tags": [],
    "source": "web",
    "createdAt": 1791965028499,
    "data": { "full_name": "Asha Rao", "city": "Pune", "seats": 2 }
  }
}
  • data holds the answers by field key.
  • X-Form-Delivery is unique per delivery — use it to ignore a repeat.
  • Times are epoch milliseconds.

Checking the signature

When the webhook has a secret, every delivery carries X-Form-Signature: sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your secret. Compute the same and compare before trusting the request.

Node.js (Express)
import { createHmac, timingSafeEqual } from 'node:crypto';

// Express: read the body raw, before any JSON parser touches it.
app.post('/datamold', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + createHmac('sha256', process.env.DATAMOLD_SECRET)
    .update(req.body) // the raw bytes
    .digest('hex');
  const given = req.get('X-Form-Signature') ?? '';
  const ok = given.length === expected.length &&
    timingSafeEqual(Buffer.from(given), Buffer.from(expected));
  if (!ok) return res.status(401).end();

  const { event, submission } = JSON.parse(req.body.toString('utf8'));
  // …your work here…
  res.status(200).end();
});
Python
import hmac, hashlib

def is_from_datamold(raw_body: bytes, header: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or '')

Answering and retries

  • Answer with any 2xx status within 10 seconds and the delivery is done.
  • Anything else — another status, a timeout, no connection — is retried, up to 6 attempts in all. Each retry waits longer than the last: at least 1 minute, then 5 minutes, 25 minutes, about 2 hours and about 10 hours.
  • Waiting retries are sent when the organization's next response arrives, and in any case during the nightly run — so a retry can come later than those minimums, never sooner.
  • Because a delivery can arrive more than once, make your handler safe to repeat: key on X-Form-Delivery or the submission id.

The Share tab shows each webhook's last attempt: when it was, and the status your server answered or the error.