Skip to content

Webhook Ingestion

Webhook ingestion is the inbound 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 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.

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 2xx while 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
  1. Create an HTTP trigger with a stable path.
  2. Configure signature validation on the trigger.
  3. Keep the function small: persist, normalize, and return.
  4. 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.

URL Shape

Current HTTP trigger URLs use:

https://api.centrali.io/workspace/{workspace}/api/v1/http-trigger/{path}

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.

All common provider examples below use hmacAlgorithm: "sha256".

Common Provider Configurations

Provider Signature header Extra headers Digest encoding Signature regex Timestamp config Secret encoding
Stripe stripe-signature hex v1=([^,]+) Embedded in stripe-signature via t=(\d+) raw
Shopify x-shopify-hmac-sha256 base64 ^(.+)$ None raw
GitHub x-hub-signature-256 hex sha256=(.+) None raw
Svix svix-signature signatureIdHeaderName: svix-id base64 ^(?:v1,)?(.+)$ Separate header: svix-timestamp prefixed-base64
Clerk svix-signature signatureIdHeaderName: svix-id base64 ^(?:v1,)?(.+)$ Separate header: svix-timestamp prefixed-base64

Clerk uses Svix under the hood, so the header model matches Svix. If your sender uses a different Standard Webhooks prefix (for example webhook-id instead of svix-id), use the exact header names your provider sends. The trigger fields are configurable.

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.

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 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.

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
Signing Secret Stripe endpoint secret (whsec_...)
Signature Header stripe-signature
HMAC Algorithm sha256
Digest Encoding hex
Signature Extraction Regex v1=([^,]+)
Replay Protection On
Timestamp Location Embedded in another header
Timestamp Header Name stripe-signature
Timestamp Extraction Regex t=(\d+)
Secret Encoding raw

Resulting URL:

https://api.centrali.io/workspace/{workspace}/api/v1/http-trigger/payments/stripe

4. Connect Stripe

  1. In the Stripe dashboard, add the Centrali HTTP trigger URL as a webhook endpoint.
  2. Select the event types you want.
  3. Copy the endpoint signing secret from Stripe.
  4. 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-events collection 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.

Diagnostics tab showing a degraded trigger with a signature validation rejection and a matching rejection breakdown

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.payload is the parsed request body. Persist the full event in a raw field 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, and customerId become visible columns.