title: Webhooks description: Receive signed, real-time notifications when events happen.
Webhooks
When a workspace enables an outbound webhook, Uniforms delivers a signed HTTP
POST to the configured URL for each event (for example, a new submission).
Each request is cryptographically signed so you can prove it came from us and
reject replays.
Verify the signature
Every webhook includes:
X-Uniforms-Signature:t=<unix-seconds>,v1=<hex-hmac>.- A raw JSON body.
To verify:
- Split the header into
t(timestamp) andv1(signature). - Reject if
tis older than your replay window (e.g. 5 minutes). - Compute
HMAC-SHA256(per-workspace-secret, "${t}.${rawBody}")and compare it tov1using a constant-time comparison.
import crypto from "node:crypto";
function verify(payload, header, secret, maxAgeSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((s) => s.split("=")));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > maxAgeSeconds) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${payload}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Event payload
{
"type": "submission.created",
"workspaceId": "ws_...",
"formId": "frm_...",
"submissionId": "sub_...",
"createdAt": "2026-06-22T12:00:00.000Z"
}
Webhooks carry references only — never secrets or full submission content. Fetch the full resource through the authenticated API if you need it.
Delivery guarantees
- At-least-once: deliver through a queue with bounded retries and backoff.
- Idempotent handling: a delivery may be retried; dedupe by event id.
- Short timeout: deliveries time out quickly; respond
2xxfast and process asynchronously.
Responding
Return any 2xx status to acknowledge. Anything else triggers a retry. Always
verify the signature before acting on the payload.
Destination safety
Destination URLs are validated for SSRF before sending: private, loopback, and
link-local ranges and the cloud metadata IP are blocked, only https is
allowed, and redirects to internal hosts are refused.