Setting up a webhook Surface
Set up a webhook Surface that receives HTTP events from an external provider, verifies their signatures, and routes each event to a Flow or Agent Capability.
Before you begin
Create a Product with at least one enabled Capability. Get access to the external provider’s webhook settings so you can register the Webhook URL and copy its signing secret.
Create a webhook Surface
To create a webhook Surface, follow these steps:
- Open the Product that receives the events.
- On the Product page, click Surfaces, then click Add Surface.
- Select Webhook.
- Enter a name for the Surface.
- Connect each Capability that handles events to the Surface. In Visual view, drag a connection from the Capability node to the Surface node, then click Save. For more information, see Connect a Capability to a Surface.
- On the Webhook tab, copy the Webhook URL.
- Register the URL in the external provider’s webhook settings.
The Surface considers only enabled Capabilities that you connected to it. When no enabled Capability is connected, a delivery returns 404, except while the Surface is learning a schema.
Provide a payload shape
A webhook Surface uses a confirmed schema to describe the events it receives. On the Webhook tab, open Setup and choose one of these options:
- Paste Schema: paste an example JSON object payload, then click Infer Schema. The Surface moves to Schema Proposed. Runtype infers the schema from the pasted object as if it were an event, so paste a real event rather than a JSON Schema or AsyncAPI document.
- Send Test: click Start Listening, then send test events from the external provider. After three observed payloads, the Surface proposes a schema that marks each field as required or optional.
The Status badge on the Webhook tab shows one of these states:
- Draft: the Surface has no observations yet.
- Listening: the Surface collects test events to infer a schema.
- Schema Proposed: the Surface has a proposed schema for you to review.
- Active: you confirmed the schema, and the Surface runs a Capability for each event.
- Drift Detected: recent payloads differ significantly from the confirmed schema.
When the Surface reaches Schema Proposed, review the proposed schema and click Confirm. To start over, click Clear to remove the observations, then send another set of test events.
After you click Start Listening or Infer Schema, and until you confirm a schema, the Surface records each event as an observation. It doesn’t run a Capability, so no execution appears in Logs. A new Surface on which you haven’t chosen a setup option runs a Capability for each event that it receives.
If you send test events with a webhook API key, use a key that can reach all Capabilities. A key scoped to specific Capabilities receives 403 while the Surface is learning a schema.
Verify webhook signatures
Configure signature verification after you confirm the schema. While the Surface is learning a schema, it rejects deliveries with 401 if a Signing Secret is saved and the request carries no webhook API key.
To verify signatures, follow these steps:
- On the Webhook tab, open Signature Verification.
- In Provider, select the provider that sends the events.
- In Signing Secret, paste the secret that the provider uses to sign requests. For SendGrid, paste the verification key from SendGrid’s signed event webhook settings.
- Save your changes.
Runtype verifies signatures only when a Signing Secret is saved. Except while the Surface is learning a schema, every request then needs a valid signature, even a request that carries a webhook API key. When verification fails, Runtype returns 401 and doesn’t run the Capability.
The supported providers send their signature in these headers:
- Standard Webhooks:
webhook-signature. - Stripe:
stripe-signature. - GitHub:
x-hub-signature-256. - Shopify:
x-shopify-hmac-sha256. - Twilio:
x-twilio-signature. - SendGrid:
x-twilio-email-event-webhook-signature. - Slack:
x-slack-signature. - Custom HMAC-SHA256 and Custom HMAC-SHA1:
x-webhook-signature.
For the custom providers, sign the raw request body with the secret. Send the signature hex-encoded for SHA-256 or base64-encoded for SHA-1, with an optional sha256= or sha1= prefix. The Signature Header field doesn’t change the header that Runtype reads.
Standard Webhooks, Stripe, SendGrid, and Slack sign a timestamp. Runtype rejects those events when the timestamp differs from the current time by more than 5 minutes. The Timestamp Tolerance (seconds) field doesn’t change this window.
Select No verification only for an internal source that doesn’t sign requests. Selecting No verification doesn’t remove a saved Signing Secret, and Runtype then verifies requests as Custom HMAC-SHA256. To turn off verification, clear Signing Secret and save before you select No verification.
With No verification and no webhook API key, anyone who has the Webhook URL can trigger the Capability.
To authenticate an internal source without signatures, open the Keys tab and click Generate Key. Send the key, which starts with wh_, as Authorization: Bearer wh_YOUR_KEY or in the X-API-Key header. Replace wh_YOUR_KEY with the full key. After the Surface has an active key, it rejects requests that carry neither a valid key nor a valid signature.
Route events to Capabilities
When one webhook Surface receives several event types, route each type to a Capability. To configure event routing, follow these steps:
- Open the Routing tab.
- Set Event Type Path to the dot path of the field that names the event, such as
typefor Stripe. - Add a route for each Event Pattern and select its Capability. A pattern such as
payment_intent.*matches a family of event types. - Optional: select a Fallback Capability for events that match no route.
- Click Save Routing.
If the Event Type Path field is missing from a payload, Runtype reads the event name from the type, event, event_type, or action field, or from the x-github-event header.
An event that matches no route and has no fallback goes to a Capability whose name matches the event name. If no name matches, the event goes to the first connected Capability. With a webhook API key scoped to specific Capabilities, it goes to the first connected Capability that the key can reach.
Read the event in a Flow
Runtype sends each event to the Capability as the user message, available as {{userMessage}}. The message is a JSON object with these fields:
event: the detected event name, orwebhookwhen Runtype can’t detect one.payload: the complete parsed request body.metadata: the event ID inwebhook_event_id, the time Runtype received the event inwebhook_timestamp, and whether the signature was verified insignature_verified.
To read nested fields from the message, parse it in a step, such as a transform step.
By default, Runtype also passes each top-level payload field as a Flow input with the same name. For example, {{customerId}} resolves when the payload has a customerId field. Runtype omits top-level fields that start with _ because that prefix is reserved.
To pass nested or renamed fields as inputs, set inputMapping in the Surface’s behavior when you define the Product in code or update the Surface through the API. Each key is a Flow input name, and each value is a dot path into the payload. This example maps two nested Stripe fields:
When you set an input mapping, Runtype passes only the mapped fields as Flow inputs. Map every field that the Flow reads as an input. The Mappings tab in the dashboard doesn’t change the inputs that a delivery passes to the Flow.
Supported request bodies
Runtype parses the request body based on its Content-Type header:
- JSON (
application/json, any+jsontype, or no content type): Runtype parses the body. Invalid JSON returns400. - Form-encoded (
application/x-www-form-urlencoded): each form field becomes a string field, such asBodyandFromfor Twilio. - Multipart (
multipart/form-data): not supported. Runtype returns400. - Any other type: the payload is
{ "raw": "BODY" }, so the only input israw.
Choose a response mode
On the Webhook tab, Response Mode defaults to Async. Choose the mode that matches what the provider expects in the response:
- Async: Runtype accepts the event, responds right away with
202, and runs the Capability afterward. Use this mode when the provider doesn’t need the execution result. - Synchronous: the provider waits for the Flow to finish and receives its result. Synchronous mode supports Flow Capabilities only. For an Agent Capability, use Async or Custom response.
- Custom response: Runtype returns a fixed response that you define and runs the Capability afterward.
An Async response looks like this example:
A Synchronous response returns the Flow result as a string in the response field. A provider that parses the reply as a bare body can’t use this wrapper, so use Custom response instead:
If the Flow fails, a Synchronous request returns 500.
To return a custom response, follow these steps:
- In Response Mode, select Custom response.
- Click a preset, or fill in the fields yourself. Twilio (empty TwiML) returns an empty TwiML document as
application/xml. Empty (204 No Content) returns status204with no body, which suits Shopify. - Optional: edit Content-Type, Status, and Body. Status must be from 200 to 599 and defaults to 200.
- Save your changes.
Retries and duplicate events
An Async or custom acknowledgment confirms that Runtype accepted the event, not that the execution succeeded. Runtype returns 200 for a duplicate delivery of an accepted event. With a custom response, a duplicate receives the same custom response.
Runtype identifies an Async duplicate by the first ID that it finds, in this order:
- The
x-github-delivery,x-shopify-webhook-id, orwebhook-idheader. - The
idorevent_idfield in the body. - The raw request body, when neither an ID header nor an ID field exists.
Duplicate suppression lasts 24 hours from acceptance. Keep the event ID unchanged when you retry a delivery. For repeated events with identical bodies, such as a scheduled integration without an event ID, send a different webhook-id header for each event.
If Runtype can’t accept an event, it returns 500 or 503, including in Custom response mode. Configure the provider to retry these errors. In Async and Custom response modes, the event and its Flow inputs together must be 8 MiB or smaller. Runtype stores the payload both in the message and as inputs, so a request smaller than 8 MiB can exceed the limit. Runtype returns 413 for a larger event.
Test the connection
To verify the connection, follow these steps:
- Send a test event from the external provider.
- If the Surface is Listening, send two more test events so that it can propose a schema.
- When the Surface reaches Schema Proposed, review the schema and click Confirm.
- Send another test event.
- In the provider’s delivery history, confirm that the response names the expected
capability. A Custom response doesn’t name the Capability. - In Logs, open the execution and inspect the input that the Flow received.
When the Surface shows Drift Detected, it keeps running a Capability for each event. Check that the Flow still handles the changed payload fields.
Troubleshoot webhook setup
Runtype rejects some deliveries before it runs a Capability, so they don’t appear in Logs. Check the provider’s delivery history for the status code and the response body.
The following table lists the rejections that you might see:
Use these checks for other problems:
- The Surface stays in Listening: send three test events with JSON object payloads, and confirm that they reach the Webhook URL.
- Unexpected routing: check Event Type Path, the route patterns, and the Fallback Capability. Without a matching route or fallback, an event goes to the Capability named after the event, or else to the first connected Capability.
- A Flow input is empty: if you set an input mapping, map every input that the Flow reads.
For provider-specific checks, see Inbound webhook rejected.
Next steps
Continue with these guides:
- What are Surfaces?: understand how a webhook Surface fits into a Product.
- Adding capabilities to a Product: attach Flows or Agents to the Product and connect them to Surfaces.
- Scoping API keys to Capabilities: limit which Capabilities a key can reach.
- Common errors and solutions: fix rejected webhook deliveries.
- Working with logs: inspect Flow executions and their inputs.