Core Features
HMAC Webhooks & Events
HMAC Webhooks & Event Subscriptions
Voieform webhooks allow you to listen for form submission events in real time. Whenever a customer submits a entry, Voieform dispatches an HTTP POST request containing the submission payload directly to your custom webhook endpoint.
HMAC SHA-256 Signature
Every payload is signed with your unique webhook secret header (
X-Voieform-Signature).Exponential Backoff Retries
Failed deliveries (HTTP 5xx or timeout) automatically retry up to 5 times over 24 hours.
Webhook Payload Schema
event-payload.jsonjson
{
"event": "submission.created",
"submission_id": "sub_98a72b1c4e",
"form_id": "frm_live_x92a",
"created_at": "2026-08-15T18:30:00Z",
"data": {
"name": "Sarah Connor",
"email": "sarah@cyberdyne.org",
"company": "Skynet Research",
"message": "We would like to request a demo of your serverless forms."
},
"files": [
{
"field": "attachment",
"filename": "requirements.pdf",
"size_bytes": 1048576,
"url": "https://storage.voieform.com/files/sub_98a72b1c4e/requirements.pdf?sig=..."
}
],
"spam_score": 0.02
}HMAC Signature Verification
To verify that a webhook request was genuinely sent by Voieform and was not tampered with, compute the HMAC SHA-256 digest using your secret key and compare it against the X-Voieform-Signature header.
Node.js / Express Verification
webhook.jsjavascript
// Node.js / Express HMAC Verification Handler
const crypto = require("crypto");
const express = require("express");
const app = express();
// Use raw body buffer for accurate signature validation
app.post("/api/voieform-webhook", express.raw({ type: "application/json" }), (req, res) => {
const signatureHeader = req.headers["x-voieform-signature"];
const webhookSecret = process.env.VOIEFORM_WEBHOOK_SECRET; // e.g. "whsec_..."
if (!signatureHeader) {
return res.status(400).send("Missing X-Voieform-Signature header");
}
// Header format: t=1700000000,v1=9f86d081884c7d659a2feaa0c...
const parts = signatureHeader.split(",");
const timestamp = parts.find(p => p.startsWith("t="))?.split("=")[1];
const signature = parts.find(p => p.startsWith("v1="))?.split("=")[1];
const payload = req.body.toString("utf8");
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac("sha256", webhookSecret)
.update(signedPayload)
.digest("hex");
if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
const event = JSON.parse(payload);
console.log("Verified Webhook Received:", event.event);
// Process event (e.g. save to DB, send Slack notification)
return res.status(200).json({ received: true });
} else {
console.error("Invalid HMAC signature match!");
return res.status(401).send("Invalid signature");
}
});Python / FastAPI Verification
webhook.pypython
# Python / FastAPI HMAC Verification Example
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, Header
app = FastAPI()
WEBHOOK_SECRET = b"whsec_your_secret_here"
@app.post("/webhook")
async def handle_voieform_webhook(request: Request, x_voieform_signature: str = Header(None)):
if not x_voieform_signature:
raise HTTPException(status_code=400, detail="Missing signature header")
body = await request.body()
parts = dict(item.split("=") for item in x_voieform_signature.split(","))
timestamp = parts.get("t")
signature = parts.get("v1")
signed_payload = f"{timestamp}.{body.decode('utf-8')}".encode("utf-8")
expected_sig = hmac.new(WEBHOOK_SECRET, signed_payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected_sig):
raise HTTPException(status_code=401, detail="Signature verification failed")
data = await request.json()
print("Valid webhook event:", data["event"])
return {"status": "ok"}Replay Attack Protection
Always check the timestamp
t=... inside the header. If the timestamp is older than 5 minutes, reject the request to prevent replay attacks.Need help?
Contact Support