All docs
3 min read

Payload

Every webhook delivery is a POST with a JSON body and a small set of headers. Same envelope for every event — the type field tells you what happened.

Headers

X-Formspring-Signature: <hex hmac-sha256 of raw body>
X-Formspring-Event:     submission.created
X-Formspring-Timestamp: 1746651750
Content-Type:           application/json
User-Agent:             Formspring-Webhook/1.0

X-Formspring-Timestamp is unix seconds at the moment we built the request. Use it to reject stale replays in addition to the HMAC check (drop anything older than ~5 minutes).

Envelope

{
  "type": "submission.created",
  "submission_id": "01HFXX0X9R7KZJVN9VS6TG2C5T",
  "form_id": "r2EdO-orF-3S",
  "form_name": "Contact",
  "received_at": "2026-05-07T16:09:10Z",
  "payload": { },
  "meta": { }
}
Field Type Notes
type string submission.created or submission.flagged.
submission_id ULID Stable, sortable, unique. Use as your idempotency key.
form_id string The form's public ID.
form_name string Display name at time of submission.
received_at ISO 8601 When we accepted the submission.
payload object The raw form fields, key/value.
meta object IP-derived data, user agent, referrer, captcha verdict.

submission.created

{
  "type": "submission.created",
  "submission_id": "01HFXX0X9R7KZJVN9VS6TG2C5T",
  "form_id": "r2EdO-orF-3S",
  "form_name": "Contact",
  "received_at": "2026-05-07T16:09:10Z",
  "payload": {
    "email": "ada@example.com",
    "name": "Ada Lovelace",
    "message": "Loved the docs."
  },
  "meta": {
    "ip_country": "GB",
    "user_agent": "Mozilla/5.0 ...",
    "referrer": "https://example.com/contact",
    "spam_score": 0.04
  }
}

submission.flagged

Same envelope, but with a reason and the spam verdict in meta.

{
  "type": "submission.flagged",
  "submission_id": "01HFXX1HEK7Q3MNTW5YH8RBZF2",
  "form_id": "r2EdO-orF-3S",
  "form_name": "Contact",
  "received_at": "2026-05-07T16:11:42Z",
  "reason": "captcha_failed",
  "payload": {
    "email": "spammer@example.com",
    "message": "buy followers"
  },
  "meta": {
    "ip_country": "??",
    "spam_score": 0.91,
    "captcha_verdict": "fail",
    "honeypot_tripped": false
  }
}

Possible reason values: spam_score, captcha_failed, honeypot_tripped, rate_limited, blocked_email, blocked_country.

File uploads

Files don't ride in the JSON body — webhooks would balloon and time out. Instead payload carries signed download URLs:

{
  "payload": {
    "email": "ada@example.com",
    "attachment": {
      "type": "file",
      "filename": "design.pdf",
      "size_bytes": 184213,
      "content_type": "application/pdf",
      "url": "https://files.formspring.io/u/abc.../design.pdf?sig=...",
      "url_expires_at": "2026-05-07T17:09:10Z"
    }
  }
}

The signed URL is good for one hour. Fetch the bytes inside that window or store them yourself.

Size limits

Limit Value
Total request body 1 MB
Single field value 64 KB
Number of fields 200

Submissions exceeding these are truncated — meta.truncated = true flags it. You'll still get a delivery; the data is just trimmed.

What's next