Public Beta: Direct engineering support available. Join Discord →
Docs
Recipes
Typeform: Send Submissions Anywhere

Send Typeform Submissions Anywhere

Time: ~10 minutes | Difficulty: Beginner | Prerequisites: A Typeform form you can edit, Slashbin account

TL;DR: A Typeform submission is not a retryable system event — it is a person who filled in your form and moved on. Put Slashbin in front of your endpoint so the submission is verified, stored, reshaped into the format your CRM actually wants, and re-deliverable long after Typeform's delivery window has closed.


The Problem

Most webhook reliability arguments are about integration hygiene. This one is about a lead.

When a Stripe event fails to land, the money still moved and the state is still in Stripe — you can reconcile it later from the API. When a Typeform submission fails to land, there is nothing to reconcile against on your side. Someone typed their name, their email, and what they need help with, hit Submit, and saw a thank-you screen. As far as they know, you have their information.

What actually stands between that person and your CRM:

  • Your endpoint has to be up at that exact moment. Typeform will re-attempt a failed delivery, but that window is finite and outside your control. Once it closes, there is no button in Typeform that re-sends that submission to your endpoint.
  • A signature mismatch fails closed and looks like nothing. If your verification code is wrong, every submission is rejected at your door and no error surfaces anywhere a salesperson would look.
  • A bad deploy is indistinguishable from a slow week. No delivery log means no way to answer "did we get any form fills on Tuesday, or was the handler broken?"
  • The payload isn't the shape you want. Answers arrive in an array, each entry keyed by a field ref, so every consumer has to walk the array before it can do anything useful — and that parsing code is one more thing that can throw and take the submission with it.

The response is still visible in Typeform's own results view, so the data is not literally destroyed. But a lead that lives only in a form-builder's results tab, and never reached the system your team works out of, is a lead you did not follow up on.


The Solution

Route Typeform through a Slashbin project. Slashbin becomes the endpoint Typeform posts to, and everything after that is yours to control:

  • The raw payload exactly as Typeform sent it — bytes and headers.
  • Signature verification at the gateway, from stored configuration rather than crypto code in your app.
  • The transform result — the flat record your Golden Model produced, or the error it threw.
  • Every delivery attempt to your CRM or warehouse — response code, response body, and timing.
  • The final disposition — delivered, retrying, or held in the Dead Letter Queue.

Outcome: Typeform only has to reach Slashbin once. From that point on, delivery to your systems is retried, inspectable, and replayable on your schedule.


Step 1: Create a Typeform Project in Slashbin

  1. Click Create New Project and select Typeform.
  2. Type: Choose Transactional — a form submission is a discrete business event, not a metric.
  3. HMAC Signing Secret — enter the secret you will paste into Typeform in Step 2. Generate a strong random string; Typeform does not create one for you.

Selecting Typeform loads its verification contract into the project:

FieldTypeform's value
Auth typeHMAC
Signature headertypeform-signature
Signature encodingbase64
Signature prefixsha256=
Signed payloadthe raw request body
Event topicevent_type, read from the body

Slashbin's gateway has no Typeform-specific code path. Typeform works because that contract is stored and applied on every request — the same mechanism that verifies Stripe, Square, Shopify and GitHub.

⚠️

sha256= does not mean hex. Typeform is the one source we support that sends a sha256= prefix over a base64 digest. GitHub sends the same sha256= prefix over a hex digest in x-hub-signature-256. If you already implemented GitHub's check and reused it for Typeform, every submission will fail verification with an error that looks exactly like a wrong signing secret — because the bytes you compare are correct and the encoding you compare them in is not. This is the single most common way a hand-rolled Typeform check fails.


Step 2: Add the Slashbin Ingestion URL as a Typeform Webhook

  1. Copy the Ingestion URL from the Slashbin project dashboard.
  2. In Typeform, open the form and go to Connect → Webhooks → Add a webhook.
  3. Endpoint: paste the Slashbin Ingestion URL.
  4. Secret: add the same secret you entered in Step 1.
  5. Toggle the webhook on, then send Typeform's test request.
  6. Open the Slashbin Delivery Log. A verified test delivery confirms the secret matches on both sides before a real person's submission depends on it.

Webhooks are configured per form. If you collect leads through three forms, point all three at the same Slashbin project — the routing in Step 3 can tell them apart.


Step 3: Reshape the Submission Into Your Destination's Format

The topic lives in the body

Typeform does not put the event type in a header. It arrives as event_type in the JSON body:

{
  "event_id": "01G8N4YZ2S3M5T7V9X1B4D6F8H",
  "event_type": "form_response",
  "form_response": {
    "form_id": "lT4Z3j",
    "token": "a3a12ec67a1365927098a606107fac15",
    "submitted_at": "2026-07-21T14:03:22Z",
    "definition": {
      "id": "lT4Z3j",
      "title": "Contact sales",
      "fields": [ "..." ]
    },
    "answers": [
      {
        "type": "text",
        "text": "Lauren Ramirez",
        "field": { "id": "hVONkQcnSNRj", "type": "short_text", "ref": "full_name" }
      },
      {
        "type": "email",
        "email": "[email protected]",
        "field": { "id": "JwWggjAKtOkA", "type": "email", "ref": "work_email" }
      },
      {
        "type": "choice",
        "choice": { "label": "50-200" },
        "field": { "id": "PNe8ZKBK8C2Q", "type": "multiple_choice", "ref": "company_size" }
      }
    ]
  }
}

That changes how you write routing rules. For GitHub you match on a header value; for Typeform you match on a JSON path into the bodyevent_type, plus form_response.form_id when one project takes submissions from several forms.

The answers array is not a record

Notice what the payload does not contain: a field called email. It contains an array where the third element might be the email on one form and the company size on another, and where the type of each entry decides which key holds the value — text, email, choice.label.

Your CRM wants a row. Define it once in the Transformation IDE instead of walking that array in every consumer:

Target fieldSource JSONPathNotes
submission_id$.form_response.tokenRequired — the per-response id, good for idempotency
form_id$.form_response.form_idRequired
submitted_at$.form_response.submitted_atISO timestamp
full_name$.form_response.answers[?(@.field.ref=='full_name')].textRequired
work_email$.form_response.answers[?(@.field.ref=='work_email')].emailRequired
company_size$.form_response.answers[?(@.field.ref=='company_size')].choice.labelOptional

Pro tip: Match on field.ref, not on array position and not on field.id. Refs are the stable names you control in the form editor; positions shift the moment someone adds a question, and ids change when a field is recreated. Setting a deliberate ref on every question you consume is the one piece of hygiene that makes this mapping survive form edits.

Toggle Required ON for submission_id, full_name, and work_email. A submission missing the email is not a lead your CRM can use — better that it is flagged as invalid and held than written as a half-formed contact.

Click Publish to activate the model. Unpublished models don't drop events: incoming submissions are captured in the Dead Letter Queue until a matching model is live, so nothing is lost while you iterate.

What your destination receives

{
  "submission_id": "a3a12ec67a1365927098a606107fac15",
  "form_id": "lT4Z3j",
  "submitted_at": "2026-07-21T14:03:22Z",
  "full_name": "Lauren Ramirez",
  "work_email": "[email protected]",
  "company_size": "50-200"
}

One flat record per submission. A CRM importer or a warehouse loader can take that directly.


Step 4: Deliver, Retry, and Replay

On the Outbound tab, click + Add Destination and point it at the system that should own the lead — your CRM's intake endpoint, a warehouse-bound consumer, or both. One submission can fan out to several destinations, each with its own retry policy.

From here the guarantees are Slashbin's, not Typeform's:

  • Transient failure → retry with backoff. Your CRM returning 502 for ninety seconds costs you nothing.
  • Exhausted attempts → the Dead Letter Queue, holding the original raw payload, not a summary of it.
  • Fixed the handler → Replay. Slashbin re-delivers from its own storage. Typeform is not involved and does not need to be — which is the entire point, because Typeform cannot re-send that submission for you.
  • Nothing arrived downstream but the log says delivered? The attempt record carries your endpoint's exact response body. The bug is past the point Slashbin can see.

Because the transform runs at the gateway, replay always operates on the original payload through the current model. Add a field to the form on Monday, map it Tuesday, and re-run Monday's submissions through the new mapping.


Why Slashbin vs a Raw Handler

  • The base64-versus-hex trap disappears. typeform-signature, base64, sha256= prefix, raw body — applied at the gateway as configuration. You never write the check, so you never write it wrong.
  • Body-based routing without body-parsing code. event_type and form_response.form_id become routing rules instead of an if-else chain at the top of your controller.
  • The answers array is flattened once. Every downstream consumer sees the same flat record, and adding a question to the form doesn't break the ones that don't use it.
  • Delivery is retried and replayable. Typeform's delivery window closes; yours doesn't have to.
  • A form fill is never a silent drop. Failed deliveries surface in the DLQ with the full payload, so "did we get any leads Tuesday?" is a query, not a guess.

Next Steps