Business

REST API

Pull submissions into your CRM, ticket system or your own scripts automatically – with a plain JSON API.

Authentication

Every request needs an API key in the Authorization header. As the owner of a Business account you create keys under "API" in your account.

curl https://www.formpost.eu/api/v1/submissions?since=2026-10-01T00:00:00Z \
  -H "Authorization: Bearer fp_..."

A key belongs to the account, not to a person, and sees all forms of the account. If the Business plan expires, the API responds with 403 until it is booked again.

Endpoints

GET /forms

All forms of the account.

GET /forms/{id}

One form. The ID is the identifier from the form address (/f/abc123).

GET /submissions

Submissions of all forms, newest first.

GET /forms/{id}/submissions

Submissions of one form.

GET /submissions/{id}

A single submission.

PATCH /submissions/{id}

Mark as spam or release.

DELETE /submissions/{id}

Delete a submission permanently.

All paths are relative to https://www.formpost.eu/api/v1. Responses are JSON.

List parameters

filter
valid (default), spam or all – the same filters as in the inbox.
since
Only submissions from this point in time (ISO 8601). Use it to regularly fetch only what is new.
page
Page, starting at 1.
per_page
Entries per page, 1 to 100 (default 50).

Responses

Lists are in data, paging details in meta.

{
  "data": [
    {
      "id": 4711,
      "form_id": "abc123",
      "state": "valid",
      "spam": false,
      "verified": true,
      "encrypted": false,
      "data": { "email": "anna@example.com", "message": "Hallo!" },
      "encrypted_data": null,
      "recipient_email": "info@example.com",
      "referer": "https://example.com/kontakt",
      "created_at": "2026-10-12T09:30:00Z"
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1, "filter": "valid" }
}

For encrypted forms, data is empty and encrypted_data contains the sealed envelope. We do not know the content through the API either – it can only be opened with your private key. More under Zero-knowledge encryption.

Mark spam

spam: true moves a submission to spam, spam: false releases it again.

curl -X PATCH https://www.formpost.eu/api/v1/submissions/4711 \
  -H "Authorization: Bearer fp_..." \
  -H "Content-Type: application/json" \
  -d '{"spam": true}'

Errors

Errors come with a matching HTTP status and a machine-readable code.

{ "error": { "code": "unauthorized", "message": "Missing or invalid API key" } }
  • 401 – Key missing, wrong or revoked.
  • 403 – The account has no active Business plan.
  • 404 – Form or submission does not exist in this account.
  • 400 – Invalid parameter, e.g. a wrong date format.
  • 429 – Too many requests – at most 120 per minute and key.

Webhooks

Instead of polling, you can have every new submission sent to you. We then POST JSON to your URL – as soon as the submission has passed spam protection, at the same time as the email.

POST https://example.com/formpost-webhook
Content-Type: application/json
FormPost-Event: submission.created
FormPost-Delivery: 5f0c2c1e-8a8e-4c4a-9b7e-2d6b1d0f7a11
FormPost-Signature: t=1760261400,v1=3b1f…

{
  "id": "5f0c2c1e-8a8e-4c4a-9b7e-2d6b1d0f7a11",
  "type": "submission.created",
  "created_at": "2026-10-12T09:30:00Z",
  "data": {
    "submission": { "id": 4711, "form_id": "abc123", "state": "valid", "data": { "email": "anna@example.com" }, … },
    "form": { "id": "abc123", "name": "Kontakt", … }
  }
}
  • Events: submission.created for new, valid submissions and ping for the test message from your account.
  • If your server does not answer with a 2xx status within 10 seconds, we retry up to six times over about nine hours. After ten consecutive failed deliveries we disable the webhook.
  • For encrypted forms, the webhook only contains the sealed envelope, just like the API.
  • Every delivery has a unique ID (FormPost-Delivery). If the same ID arrives twice, process it only once.

Verify the signature

Every message is signed with the webhook secret (HMAC-SHA256 over timestamp, dot and body). Check the signature before processing anything – that way your server knows the message really comes from us.

<?php
$secret = 'whsec_...';
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_FORMPOST_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $sig); // ['t' => ..., 'v1' => ...]

$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, $secret);
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// $event['data']['submission'] verarbeiten …
http_response_code(200);

Get started

The REST API is included in the Business plan – together with team access and up to 1,000 forms.

Create an API key