Webhook Ingestion¶
Webhook ingestion is the inbound, asynchronous half of Centrali's webhook story. Use an HTTP trigger to receive third-party events, validate their signatures before your code runs, persist accepted payloads as records, and hand downstream work to functions or automations.
Note
This page covers incoming webhooks from Stripe, Shopify, GitHub, Svix, Clerk, or custom providers. For outbound webhooks that Centrali sends to your customers or partner systems, see Webhooks.
Tip
Need a synchronous request/response API instead of queued webhook processing? Use an Endpoint trigger. Endpoints are the synchronous API surface; HTTP triggers are the async ingest surface.
Why Ingest at the Backend Layer¶
Verifying signatures, storing payloads, and fanning work out to multiple systems are backend concerns. Handling them once at the platform layer gives you:
- One signature-validation surface instead of bespoke verification code in every service
- A durable event log you can query later instead of a provider-controlled retention window
- Asynchronous processing so the sender gets a fast
2xxwhile your business logic runs on your schedule
The Pattern¶
Provider webhook
-> HTTP trigger URL
-> trigger-layer signature validation
-> queued function run
-> executionParams.payload
-> record in a collection
-> automations, queries, search, outbound webhooks
- Create an HTTP trigger with a stable path.
- Configure signature validation on the trigger.
- Keep the function small: persist, normalize, and return.
- Let Automations or later function runs handle side effects.
When Centrali accepts the request it queues a function run and returns 202 with { "status": "queued" }. Invalid signatures are rejected before your function runs.
Idempotency and Duplicate Deliveries¶
Most webhook providers deliver at least once and retry on failure or timeout. That means your function should assume the same provider event can arrive more than once.
For inbound webhook flows:
- Persist a stable provider event identifier such as Stripe's
event.id - Make the first write idempotent so a retried webhook does not create duplicate records
- Trigger downstream side effects from the stored record or a later automation step, not from fragile in-memory assumptions
If you store first and react later, you get one durable place to deduplicate, inspect, replay, and audit provider events.
URL Shape¶
Current HTTP trigger URLs use:
Signature Validation¶
Signature verification is configured on the HTTP trigger, not in your function. By the time your code runs, Centrali has already:
- Read the raw request body
- Extracted signature and optional replay-protection metadata from headers
- Validated the HMAC
- Rejected stale timestamped requests when replay protection is enabled
Your function only receives the parsed request body as executionParams.payload.
Note
As of 6.1, HTTP triggers use the same signature validator as endpoint triggers. You no longer need to switch to an endpoint trigger just to get native signature verification.
Built-in Provider Presets¶
For common webhook senders, choose a provider preset in the console or set provider in the trigger metadata. Presets fill the header names, signature extraction, digest encoding, secret encoding, and replay defaults for you.
Built-in presets:
svixstripeslackgithubshopify
Use the Svix preset for Svix itself and for providers built on Svix, including Clerk, Resend, Loops, OpenAI, and Brex.
Common Provider Configurations¶
| Preset | Signature header | Extra headers / replay data | Digest encoding | Notes |
|---|---|---|---|---|
stripe | stripe-signature | Timestamp extracted from the same header via t=(\d+) | hex | Good default for Stripe event endpoints |
shopify | x-shopify-hmac-sha256 | None | base64 | Raw secret |
github | x-hub-signature-256 | None | hex | Extracts sha256=... |
slack | x-slack-signature | x-slack-request-timestamp | hex | Signs v0:{timestamp}:{body} |
svix | svix-signature | svix-id, svix-timestamp | base64 | Uses prefixed-base64 secret decoding |
Any HMAC-based scheme
If your provider is not listed, you can still use Centrali as long as you know the signature header, digest encoding, any signature-id or timestamp headers, and whether the secret should be used raw or decoded from a prefixed value.
Canonical Trigger Metadata¶
Recommended preset-backed configuration:
{
"path": "clerk-webhook",
"validateSignature": true,
"provider": "svix",
"signingSecret": "whsec_...",
"params": {
"source": "clerk"
}
}
Advanced raw configuration when no preset fits:
{
"path": "custom-webhook",
"validateSignature": true,
"signingSecret": "whsec_...",
"signatureHeaderName": "x-custom-signature",
"signatureIdHeaderName": "x-custom-id",
"timestampHeaderName": "x-custom-timestamp",
"timestampExtractionPattern": "^t=(\\d+),",
"extractionPattern": "^v1=([^,]+)$",
"hmacAlgorithm": "sha256",
"hmacEncoding": "hex",
"secretEncoding": "raw",
"payloadFormat": "default",
"replayToleranceSec": 300
}
replayToleranceSec defaults to 300 seconds when you do not override it.
MCP / AI Agent Setup¶
The MCP create_trigger and update_trigger tools accept the same triggerMetadata shape shown above. Recommended setup flow for AI agents:
- Create the HTTP trigger with
{ path, provider, validateSignature: true } - Register the resulting URL with the upstream provider
- Update the trigger with the provider-issued
signingSecret
Until the secret is set, Centrali rejects requests safely instead of letting unsigned traffic through.
Worked Example: Store Stripe Events as Records¶
The core pattern is: accept the event, persist it as data, and process it later.
1. Create a Collection¶
Create a stripe-events collection in schemaless mode so it can accept different Stripe event shapes. After events start landing, use Schema Discovery to promote the fields you care about into visible columns.
2. Write the Function¶
Create a function called store-stripe-event:
async function run() {
const event = executionParams.payload;
const existing = await api.queryRecords("stripe-events", {
filter: { "data.eventId": event.id },
pageSize: 1
});
if (existing.data.length > 0) {
return { success: true, duplicate: true, recordId: existing.data[0].id };
}
const record = await api.createRecord("stripe-events", {
eventId: event.id,
eventType: event.type,
created: event.created,
livemode: event.livemode,
customerId: event.data?.object?.customer,
amount: event.data?.object?.amount,
currency: event.data?.object?.currency,
raw: event,
});
return { success: true, recordId: record.data.id };
}
This function does not verify signatures itself. That happens at the trigger layer. Its job is to store the event and expose the fields you'll query later. It also short-circuits duplicate deliveries by checking the provider event ID before inserting a new record.
3. Create the HTTP Trigger¶
In the Centrali console, create an HTTP trigger with these settings:
| Setting | Value |
|---|---|
| Name | stripe-webhook |
| Function | store-stripe-event |
| Type | HTTP |
| Path | payments/stripe |
| Validate Signature | On |
| Provider preset | stripe |
| Signing Secret | Stripe endpoint secret (whsec_...) |
| Replay Protection | On (5-minute default tolerance) |
Resolved trigger metadata:
{
"path": "payments/stripe",
"validateSignature": true,
"provider": "stripe",
"signingSecret": "whsec_..."
}
Resulting URL:
4. Connect Stripe¶
- In the Stripe dashboard, add the Centrali HTTP trigger URL as a webhook endpoint.
- Select the event types you want.
- Copy the endpoint signing secret from Stripe.
- Paste that secret into the Centrali trigger configuration.
At that point Stripe sends signed requests to Centrali, the trigger validates them, and accepted events arrive in your function as executionParams.payload.
5. Process Asynchronously¶
Once the raw event is stored, use one of these follow-up patterns:
- Add an Automation that branches on
eventType - Attach event-driven triggers to the
stripe-eventscollection for downstream actions - Query the collection directly for analytics, support tooling, audits, or reconciliation
- Emit your own outbound webhooks after internal processing is complete
Debugging¶
Diagnostics tab¶
Open the trigger's Diagnostics tab (Triggers → your trigger → Diagnostics) for a request-level view of what's happening before your function ever runs. The page separates inbound requests from executed runs, so you can see at a glance when a signing secret, timestamp tolerance, or header mapping is rejecting traffic before it reaches your code.

In the screenshot above the trigger is marked Degraded because the last 7 days contain 3 requests rejected across 2 issues (missing request body and signature validation failed). The status banner surfaces the most actionable next step (Review signature config), and the Rejection breakdown panel shows the specific reasons, first/last seen, and the raw backend error — here, a malformed prefixed-base64 signing secret.
Use the Diagnostics tab first when a webhook "isn't working" — it will tell you whether the request was rejected before execution, failed during execution, or never arrived at all.
Other signals¶
- No function run appears: the request was rejected at the trigger layer. The Diagnostics tab will show the reason (signature mismatch, missing body, malformed secret, unknown path). Signature failures return
400, and the function is never queued. - The function ran but the data is wrong: inspect the function run details and your
api.log()output.executionParams.payloadis the parsed request body. Persist the full event in arawfield if you want to inspect or query it later. - You need to replay an accepted event: rerun the stored HTTP-trigger function run with Function Re-run. HTTP-trigger runs keep the original payload.
- You need to replay a rejected event: replay it from the provider's delivery log or dashboard after fixing the trigger config. Rejected requests never become Centrali runs.
- Your records table only shows system fields: run Schema Discovery after events arrive so fields like
eventType,amount, andcustomerIdbecome visible columns.
What to Read Next¶
- Need the broader execution model, including endpoint triggers and scheduled triggers? Read Triggers.
- Need the code-level function environment and built-in APIs? Read Functions and Writing Functions.
- Need the exact payload shapes your function receives? Read Trigger Parameters & Payload Shapes.
- Need branching, delays, approvals, or multi-step follow-up work after ingestion? Read Automations.
- Need to retry accepted webhook runs? Read Function Re-run.
- Need Centrali to send webhooks outward after processing? Read Webhooks.
- Want a concrete event-storage example? Read Store Stripe Webhook Events and Query Them Forever.