Webhooks let you receive a real-time POST request to your server every time a form is submitted. Use them to sync leads to your CRM, trigger automations, or save data to your own database.
Setup
- Open the Vlozi dashboard → Forms → your form → Settings → Webhook.
- Enter your endpoint URL (must be publicly reachable via HTTPS).
- Save. Vlozi generates a unique signing secret for this form automatically.
Payload
Every webhook delivery is a POST with Content-Type: application/json:
{
"event": "submission.created",
"formId": "form_abc123",
"formName": "Contact Us",
"submissionId": "sub_xyz789",
"submittedAt": "2026-06-28T10:00:00.000Z",
"data": {
"email": "visitor@example.com",
"message": "Hello!"
},
"meta": {
"referer": "https://example.com/contact",
"utm_source": "newsletter"
}
}File fields in data include a url for the uploaded file:
{
"resume": {
"__file": true,
"name": "resume.pdf",
"size": 184320,
"type": "application/pdf",
"url": "https://cdn.vlozi.app/…/resume.pdf"
}
}Verifying the signature
Every delivery includes an X-Vlozi-Signature header. Always verify it before trusting the payload.
The signature is computed over `${timestamp}.${rawBody}` — the X-Vlozi-Timestamp
header value, a literal ., then the exact request body. Binding the timestamp in lets you
reject stale (replayed) deliveries.
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhook(
rawBody: string,
signature: string,
timestamp: string,
secret: string,
): boolean {
const expected = "sha256=" + createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
const sigBuf = Buffer.from(signature);
const expBuf = Buffer.from(expected);
if (sigBuf.length !== expBuf.length) return false;
return timingSafeEqual(sigBuf, expBuf);
}IMPORTANT
Compute the HMAC over the raw request body bytes (prefixed with the timestamp) before
calling JSON.parse. Always use timingSafeEqual to prevent timing attacks. Optionally
reject deliveries whose X-Vlozi-Timestamp is more than a few minutes old.
Express example
import express from "express";
const app = express();
app.post("/vlozi-webhook", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-vlozi-signature"] as string;
const ts = req.headers["x-vlozi-timestamp"] as string;
if (!verifyWebhook(req.body.toString(), sig, ts, process.env.VLOZI_WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body.toString());
console.log("New submission:", event.submissionId);
res.sendStatus(200); // respond quickly — Vlozi retries on non-2xx
});Request headers
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
Vlozi-Forms/1.0 |
X-Vlozi-Form-Id |
Your form's ID |
X-Vlozi-Submission-Id |
The submission's ID |
X-Vlozi-Timestamp |
Unix timestamp (ms) of the delivery attempt |
X-Vlozi-Signature |
sha256=<hex> HMAC of `${timestamp}.${rawBody}` |
Retries
If your endpoint returns a non-2xx status (or times out), Vlozi retries up to 3 times:
| Attempt | Delay |
|---|---|
| 1st | Immediate |
| 2nd | 1 second |
| 3rd | 3 seconds |
After 3 failed attempts the delivery is marked as failed. You can view the delivery log in the dashboard under Forms → Settings → Webhook deliveries.
TIP
Respond with 200 as quickly as possible, then do your processing asynchronously. If your handler takes too long, the request may time out and trigger a retry.
Delivery log
Open the Vlozi dashboard → Forms → your form → Settings → Webhook deliveries to see the last 20 delivery attempts, their HTTP status codes, and response bodies.