HMAC Webhooks & Events
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.
Ctrl+I
HMAC Webhooks & Events | Voieform