← All guides
TypeformJuly 12, 20266 min read

Test Typeform Webhooks on Localhost with Relayers

Receive, route, and transform Typeform webhooks on your machine. A step-by-step guide to testing a Typeform webhook localhost setup with signature checks.

Typeform fires a form_response webhook the moment someone submits a form. That's great in production, but it's miserable to develop against. Typeform needs a public HTTPS URL, so your handler running on localhost:3000 is invisible to it. The usual workarounds—deploying half-baked code to a staging box, or pasting sample JSON into your tests and hoping it matches reality—both waste time and hide bugs. Worse, if you enabled a signing secret, you can't verify the Typeform-Signature header without the exact bytes Typeform actually sends.

This guide wires Typeform straight to your laptop with Relayers. You get a stable public URL, JQ-based routing, payload transformation, and a tunnel to localhost—all while the original request body and headers arrive untouched, so signature verification keeps working.

What you'll build

Typeform only ever sees a single, permanent Relayers URL. Relayers relays each delivery down a tunnel to whatever port your app is listening on.

Prerequisites

1Create an endpoint in Relayers

Open the Relayers app and go to Endpoints. Create a new endpoint—this gives you a public ingest URL shaped like:

https://api.relayers.app/v1/webhooks/wep_8f3c2a1b9d

Copy that URL with the Copy button. Before touching Typeform, hit Send test from the Endpoints view to confirm the endpoint is live and shows up in your Events list. This is the URL Typeform will call, and it never changes even as you reconfigure routing behind it.

2Add the webhook in Typeform

You can register the webhook two ways.

Through the UI: open your form, go to the Connect tab, choose Webhooks, and click Add a webhook. Paste your Relayers URL. If you want signed deliveries, set a secret—Typeform will use it to sign every request.

Through the Create Webhook API: if you automate form setup, register it programmatically:

curl -X PUT \
  "https://api.typeform.com/forms/<form_id>/webhooks/relayers-dev" \
  -H "Authorization: Bearer $TYPEFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.relayers.app/v1/webhooks/wep_8f3c2a1b9d",
    "enabled": true,
    "secret": "your-signing-secret",
    "verify_ssl": true
  }'

When a secret is set, Typeform signs the payload and sends a Typeform-Signature header formatted as sha256=<base64 HMAC-SHA256 of the raw body>. Keep the secret handy—you'll verify against it locally in step 5's signature note.

3Forward events to localhost

Now bridge the endpoint to your machine. In a terminal:

relayers login
relayers listen --forward localhost:3000

relayers listen opens a WebSocket tunnel and streams matching deliveries to localhost:3000. Submit a test response to your Typeform form and watch it land on your handler within a second or two. You can also do this entirely from the desktop app: in Rules, set the destination to a tunnel pointing at localhost:3000.

Every request Relayers forwards carries the original body and headers untouched, so your handler sees exactly what Typeform sent—Typeform-Signature included.

4Route and filter with JQ

If several forms point at the same endpoint, or you only care about one form during development, add a Rule with a JQ filter. Rules are evaluated top to bottom and first match wins, so order them from most specific to least. You can reorder them by dragging.

To accept only responses from a single form:

{ "form_response": { "form_id": "abc123XY" } }

Anything that doesn't match this shape is skipped by the rule. Point the matching rule's destination at your localhost:3000 tunnel and leave broader forms to a lower-priority rule (or drop them entirely while you develop).

5Transform the payload

Typeform's form_response.answers[] array is verbose: each answer is an object with a field descriptor and a typed value (text, email, choice, and so on). For local testing you usually want a flat object keyed by your field refs.

In Transformations, create a jq transform and use the test playground to iterate against a real captured payload:

{
  form_id: .form_response.form_id,
  submitted_at: .form_response.submitted_at,
  answers: (
    .form_response.answers
    | map({
        field: .field.ref,
        value: (.text // .email // .choice.label)
      })
  )
}

The // operator falls through answer types—.text for short/long text, .email for email fields, .choice.label for single choice—so each answer collapses to a simple { field, value } pair. Relayers supports jq, jsonata, javascript, and go_template; pick whichever you're fastest in. The playground shows the output before you save, so you never ship a broken mapping.

A note on signatures

Because Relayers forwards the raw request bytes, you can verify the Typeform-Signature header on localhost exactly as you would in production. Recompute base64(HMAC-SHA256(secret, raw_body)), prefix it with sha256=, and compare against the header. Verify against the raw body, not a re-serialized JSON object—reparsing changes the bytes and breaks the HMAC. Transformations run after delivery routing, so if you need the untransformed body for verification, point your tunnel at the raw endpoint and transform downstream.

Going to production

When your handler is ready to deploy, you don't touch Typeform again. Just change the Rule's destination from the localhost:3000 tunnel to your public URL (for example https://api.yourapp.com/hooks/typeform). The Relayers endpoint URL Typeform calls stays identical. You can export your endpoint, rules, and transformations as a JSON bundle and import them into another workspace or commit them alongside your code, so staging and production stay in sync.

Troubleshooting

Wrap-up

With Relayers in the middle, Typeform webhooks reach your laptop over a stable URL, you filter to just the form you care about with JQ, flatten the answers into something usable, and keep signature verification intact the whole way through. When you're done developing, flip one destination and you're in production—no Typeform reconfiguration required.

Ready to test Typeform webhooks locally? Get started with Relayers and download the app—the free tier has everything you need.