FreelyForm

Webhooks

Form submissions as signed webhook events.

Move a submission into a CRM, helpdesk, spreadsheet service, or custom automation without polling the API.

Events

EventWhen it fires
submission.receivedA submission is accepted and stored.
submission.partialProgress is saved before completion.
submission.deliveredThe delivery pipeline finishes.
submission.failedA delivery channel fails permanently.
submission.spamA submission is classified as spam.

Verify the signature

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

const header = req.headers['x-freelyform-signature'];
const [tsPart, sigPart] = header.split(',');
const timestamp = tsPart.slice(2);
const received = sigPart.slice(3);

const expected = createHmac('sha256', process.env.FREELYFORM_SECRET)
  .update(timestamp + '.' + rawBody)
  .digest('hex');

const ok =
  received.length === expected.length &&
  timingSafeEqual(Buffer.from(received), Buffer.from(expected));

Read the raw request body before parsing JSON. A framework that re-serializes the body will change the bytes and break the signature.

Delivery behaviour

  • HTTP endpoints must use HTTPS; plain HTTP is accepted only for localhost during development.
  • Discord and Telegram destinations use their incoming webhook URLs.
  • Each attempt is recorded with status and response detail.
  • Five attempts are made before an event is marked failed.
  • Replay re-queues the same event after the receiver is fixed.

Webhook or API?

Use a webhook for near-real-time fan-out and an API query for reconciliation. The two complement each other: webhooks can be missed during downtime, while the submission record remains available for a later fetch.

Webhook questions

Which events can trigger a webhook?

Submissions can emit received, partial, delivered, failed, and spam events. Choose the events each endpoint should receive.

How are webhook payloads authenticated?

Each request includes an x-freelyform-signature header in the form t=<timestamp>,v1=<hmac>. Compute HMAC-SHA256 over the timestamp, a dot, and the raw body using the endpoint secret, then compare in constant time.

What happens when my endpoint is down?

Failed attempts are retried up to five times and recorded in the delivery log. You can replay a delivery from the dashboard or API after fixing the receiver.

Put your forms on a backend that keeps a record.

Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.

POST https://freelyform.com/f/FORM_ID