DEV Community

Mary Olowu
Mary Olowu

Posted on • Originally published at centrali.io

Ingest Webhooks From Any Provider — GitHub as the Example

Centrali can store webhook events from any provider that sends HTTP POST requests. The signature settings are configurable per trigger, so each provider gets its own verification rules — Stripe, GitHub, Shopify, Twilio, or anything else.

This walkthrough uses GitHub as the example: one function, one trigger, signature verification, permanent storage. The same pattern works for any provider.

How GitHub Webhook Signatures Work

GitHub signs every webhook delivery with HMAC-SHA256. The signature arrives in the x-hub-signature-256 header, prefixed with sha256=:

x-hub-signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
Enter fullscreen mode Exit fullscreen mode

This is different from Stripe's compound header (t=...,v1=...), which is why signature settings are per-trigger rather than a global config. Every provider has its own format.

Step 1: Write the Store Function

In the Centrali console, go to Logic > Functions and create a new function called Store GitHub Event.

Function editor with Store GitHub Event code

async function run() {
  const payload = executionParams.payload;
  const headers = executionParams.headers || {};

  const eventType = headers['x-github-event'] || 'unknown';
  const deliveryId = headers['x-github-delivery'] || null;

  const record = await api.createRecord('github-events', {
    eventType: eventType,
    deliveryId: deliveryId,
    sender: payload.sender?.login || null,
    action: payload.action || null,
    repo: payload.repository?.full_name || null,
    raw: payload,
  });

  api.log({ message: 'Event stored', eventType, repo: payload.repository?.full_name,
    recordId: record.data.id });

  return { success: true, recordId: record.data.id };
}
Enter fullscreen mode Exit fullscreen mode

A few things to note:

  • executionParams.payload is the raw HTTP body GitHub sends. For HTTP triggers, this is the full POST body — no wrapping.
  • executionParams.headers gives you the request headers. GitHub puts the event type in x-github-event and a unique delivery ID in x-github-delivery.
  • The function flattens key fields (eventType, sender, repo, action) to the top level for easy filtering and keeps the full payload in raw.

Before running this, create a schemaless collection called github-events. Schemaless mode accepts any shape — a push event looks nothing like an issues event.

Tip: Want to follow along? Create a free workspace — this takes less than 5 minutes, no deployment required.

Step 2: Create the HTTP Trigger

Go to Logic > Triggers and create a new trigger.

Trigger detail showing HTTP trigger URL and signature verification enabled

Field Value
Name github-webhook
Function Store GitHub Event
Type HTTP Trigger
Path github

This gives you a public webhook URL:

https://api.centrali.io/data/workspace/{your-workspace}/api/v1/http-trigger/github
Enter fullscreen mode Exit fullscreen mode

Configure Signature Verification

GitHub uses a simpler signature format than Stripe — no compound header, no timestamp. Toggle Validate Signature on and configure:

Signature configuration with x-hub-signature-256 header and sha256 extraction

Setting Value
Validate Signature On
Signing Secret Your GitHub webhook secret (you'll set this in GitHub too)
Signature Header Name x-hub-signature-256

Expand Advanced Signature Settings to see the extraction defaults:

Advanced signature settings showing sha256 algorithm, hex encoding, and extraction regex

Setting Value
HMAC Algorithm sha256
Digest Encoding hex
Signature Extraction Regex sha256=(.+)
Secret Encoding raw (default)

The extraction regex sha256=(.+) strips the sha256= prefix from GitHub's header value, leaving just the HMAC digest for verification.

Note: GitHub doesn't include a timestamp in the signature header, so there's no replay protection timestamp to configure. If you need replay protection, you can enable it separately — Centrali will track delivery IDs and reject duplicates.

Step 3: Configure the Webhook in GitHub

Open your repository on GitHub. Go to Settings > Webhooks > Add webhook.

GitHub webhook settings page with Centrali URL and JSON content type

Field Value
Payload URL Your Centrali HTTP trigger URL
Content type application/json
Secret The same secret you entered in the Centrali trigger
Events Choose which events to receive

Click Add webhook. GitHub sends a ping event immediately to verify the endpoint is reachable.

Step 4: See Your Data

Push a commit, open a pull request, or create an issue — any event you subscribed to. Then open the github-events collection in the Centrali console.

Collection view showing stored GitHub events with eventType, sender, and repo columns

Click into any record to see the flattened fields and the full raw payload:

Record detail showing push event with sender, repo, and raw JSON payload

Toggle JSON editor to see the complete payload GitHub delivered:

JSON editor view showing the full GitHub webhook payload

You can verify the delivery on GitHub's side too. Go to your webhook settings and click Recent Deliveries — you'll see the response status and headers:

GitHub recent deliveries showing successful 202 response from Centrali

Note: After your first events arrive, run Schema Discovery on the github-events collection to turn the auto-detected fields into filterable, sortable columns.

The Pattern Works for Any Provider

The function + trigger pattern is the same regardless of the provider. The only things that change are:

Provider Signature Header Extraction Regex Secret Format
Stripe stripe-signature v1=([^,]+) whsec_... (raw)
GitHub x-hub-signature-256 sha256=(.+) Any string (raw)
Shopify x-shopify-hmac-sha256 (entire header) API secret (base64)
Twilio Uses URL-based auth

For providers that don't sign webhooks at all, just leave signature verification off. The function and collection work the same way.

Query Programmatically

import { CentraliSDK } from '@centrali-io/centrali-sdk';

const client = new CentraliSDK({
  workspaceId: 'your-workspace',
  clientId: process.env.CENTRALI_CLIENT_ID,
  clientSecret: process.env.CENTRALI_CLIENT_SECRET,
});

// All push events
const pushes = await client.queryRecords('github-events', {
  'data.eventType': 'push',
});

// Events from a specific repo
const repoEvents = await client.queryRecords('github-events', {
  'data.repo': 'your-org/your-repo',
});

// Pull request events from a specific user
const userPRs = await client.queryRecords('github-events', {
  'data.eventType': 'pull_request',
  'data.sender': 'octocat',
});
Enter fullscreen mode Exit fullscreen mode

What's Next

Start building your own webhook pipeline

Top comments (0)