Skip to main content
Spacebrain Help

Verify Form webhook signatures

For developers - store the Form webhook secret, check each request's signature, ignore repeated deliveries, respond quickly and handle retries safely.

When you send form responses to your own server with a Webhook, Spacebrain signs each request so your server can check it really came from Spacebrain. This guide is for the developer who builds that server endpoint. It covers the Webhook destination in a form's Connect tab only, not Zapier, Make, Slack or Google Sheets, and not webhooks from email, payments, messaging or Webinars.

Before you connect

  • Deploy a public HTTPS endpoint that accepts POST with application/json.
  • Ensure it can read the untouched request bytes before JSON parsing or body transformation.
  • Choose durable storage for processed delivery identifiers or business-object identifiers.
  • Make sure that processing the same delivery twice creates only one record.
  • Store secrets in a secret manager, never source control, logs, screenshots, or support tickets.

When you create the Form Webhook connection, Spacebrain displays its signing secret once. Copy it directly into the receiver's secret manager. If it is lost or exposed, disconnect and create a replacement; do not attempt to recover it from logs.

Check the signature on the raw body

For a Form Webhook delivery, use the signature header and algorithm shown by the current connection setup. The receiver must compute its digest from the raw HTTP body bytes, before parsing or reserializing JSON, and compare values with a constant-time function.

The currently documented Spacebrain HMAC format is:

X-SpaceBrain-Signature: sha256=<lowercase hexadecimal HMAC-SHA256>

The signed value is the entire raw JSON request body. Even semantically equivalent JSON produces a different digest if whitespace, key order, or escaping changes.

Node.js example

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

export function verifySpacebrainSignature(rawBody, signatureHeader, secret) {
  if (!Buffer.isBuffer(rawBody)) throw new TypeError('rawBody must be a Buffer');
  if (typeof signatureHeader !== 'string' || !signatureHeader.startsWith('sha256=')) {
    return false;
  }

  const receivedHex = signatureHeader.slice('sha256='.length);
  if (!/^[a-f0-9]{64}$/.test(receivedHex)) return false;

  const expected = createHmac('sha256', secret).update(rawBody).digest();
  const received = Buffer.from(receivedHex, 'hex');

  return received.length === expected.length && timingSafeEqual(received, expected);
}

In Express, use a route-specific raw-body parser for this endpoint. Do not call JSON.stringify(req.body) and verify that reconstructed value.

Python example

import hashlib
import hmac


def verify_spacebrain_signature(raw_body: bytes, header: str | None, secret: str) -> bool:
    prefix = "sha256="
    if not header or not header.startswith(prefix):
        return False

    received = header[len(prefix):]
    if len(received) != 64:
        return False

    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, expected)

Reject a missing, malformed, or mismatched signature before parsing or processing the payload. Return a non-2xx status and log only a safe delivery reference, time, and failure class—not the signature, secret, or full respondent body.

Handle repeated deliveries

A valid signature proves the body was signed with your secret. It doesn't prove the request is new: the header has no signed timestamp, and Spacebrain doesn't enforce a replay window. Your server needs to spot repeats itself.

  1. Extract a stable delivery, submission, or business-object identifier from the current test payload when one is provided.
  2. Record that identifier with a unique constraint (or another all-or-nothing check) before you create records or send messages.
  3. If the same identifier arrives again with the same intended operation, return success without creating a second record or message.
  4. If no stable identifier is available in the live payload, derive a receiver-owned deduplication key from the intended business operation and ask Spacebrain support to confirm the safest field before production launch.
  5. Do not deduplicate on respondent name alone.

Keep the retention window long enough for operational retries and manual reconciliation. Choose it from your own data and incident requirements; this guide does not promise a Spacebrain replay window.

Respond quickly and process safely

Check the content type, body size, signature, payload shape and whether you've seen this delivery before. Then return a successful 2xx response promptly and move long-running work to your own durable queue when possible.

A timeout or 5xx does not prove that the receiver made no change. Before replaying or manually testing again, inspect receiver logs and the downstream system for the same stable identifier.

In the form's Connect tab, open the destination's menu and choose Activity. Recent delivery activity can show In progress, Delivered, Needs attention, an attempt count and the latest HTTP status. Don't rely on a particular number of retries or retry schedule. If a delivery shows Needs attention, find out what your server did with it before you try again.

Test your endpoint

  1. Create the Webhook connection and store the one-time secret.
  2. Choose Send test from the connection's menu in Connect.
  3. Capture the raw body in a private test environment and verify the signature.
  4. Confirm a modified body fails verification.
  5. Confirm a missing/malformed signature fails.
  6. Send the same valid test twice and prove the receiver creates only one downstream outcome.
  7. Simulate a timeout or 5xx after the downstream action, then prove reconciliation prevents duplication.
  8. Submit a real internal Form response and match its response, delivery-history row, receiver record, and downstream outcome.
  9. Remove or label test data under the applicable retention policy.

Troubleshooting

SymptomVerify
Every signature failsCorrect connection secret, raw bytes rather than parsed JSON, UTF-8 handling, exact sha256= prefix, and lowercase hex digest
Test passes but live delivery failsRoute/body middleware, payload size, schema assumptions, environment secret, and receiver timeout
Duplicate downstream recordYour duplicate check, two connections pointing at the same endpoint, a timeout after your server already saved the record, and manual retries
Delivery shows Needs attentionLatest HTTP status, receiver failure class, downstream result, and duplicate risk before retry
Secret is lost or exposedDisable/disconnect the destination, create a replacement, update the receiver, and rerun every test

Next step

If deliveries still fail, work through Troubleshoot webhooks, Zapier, and Make.

Next step

Keep moving

Open the relevant Spacebrain screen or contact support if you need help.

Last updated on

On this page