Providers
Documentation for GitHub and Ragie webhook providers with all supported events.
Providers
Providers define the webhook source, including event types, payload schemas, and signature verification.
GitHub
bash npm install @better-webhook/github bash pnpm add @better-webhook/github bash yarn add @better-webhook/github import { github } from "@better-webhook/github";
const webhook = github({ secret: process.env.GITHUB_WEBHOOK_SECRET }).event(
"push",
async (payload) => {
console.log(`Push to ${payload.repository.full_name}`);
},
);Events
push
Triggered when commits are pushed to a repository branch or tag.
.event("push", async (payload) => {
console.log(payload.ref); // "refs/heads/main"
console.log(payload.repository.name); // "my-repo"
console.log(payload.commits.length); // Number of commits
console.log(payload.pusher.name); // Who pushed
for (const commit of payload.commits) {
console.log(commit.message);
console.log(commit.author.email);
}
})Key payload fields:
ref— Full git ref (e.g.,refs/heads/main)before/after— Commit SHAs before and after pushcommits— Array of commit objectsrepository— Repository informationpusher— User who pushed
pull_request
Triggered when a pull request is opened, closed, merged, edited, etc.
.event("pull_request", async (payload) => {
console.log(payload.action); // "opened", "closed", etc.
console.log(payload.number); // PR number
console.log(payload.pull_request.title); // PR title
console.log(payload.pull_request.state); // "open" or "closed"
console.log(payload.pull_request.merged_at); // Merge timestamp (if merged)
if (payload.action === "opened") {
// New PR opened
}
})Key payload fields:
action— What happened (opened,closed,synchronize,labeled, etc.)number— Pull request numberpull_request— Full PR object withtitle,body,state,head,baserepository— Repository informationsender— User who triggered the event
issues
Triggered when an issue is opened, closed, edited, labeled, etc.
.event("issues", async (payload) => {
console.log(payload.action); // "opened", "closed", etc.
console.log(payload.issue.number); // Issue number
console.log(payload.issue.title); // Issue title
console.log(payload.issue.state); // "open" or "closed"
console.log(payload.issue.labels); // Array of labels
})Key payload fields:
action— What happened (opened,closed,edited,labeled, etc.)issue— Full issue object withnumber,title,body,state,labelsrepository— Repository informationsender— User who triggered the event
installation
Triggered when a GitHub App is installed, uninstalled, or has permissions changed.
.event("installation", async (payload) => {
console.log(payload.action); // "created", "deleted", etc.
console.log(payload.installation.id); // Installation ID
console.log(payload.installation.account.login); // Account name
console.log(payload.repositories); // Repos (for "created" action)
})Key payload fields:
action—created,deleted,suspend,unsuspend,new_permissions_acceptedinstallation— Installation details withid,account,permissionsrepositories— Array of accessible repos (only forcreatedaction)
installation_repositories
Triggered when repositories are added to or removed from an installation.
.event("installation_repositories", async (payload) => {
console.log(payload.action); // "added" or "removed"
console.log(payload.repositories_added); // Repos added
console.log(payload.repositories_removed); // Repos removed
})Signature Verification
GitHub uses HMAC-SHA256 signatures sent in the X-Hub-Signature-256 header. The SDK verifies this automatically when a secret is configured.
Ragie
bash npm install @better-webhook/ragie bash pnpm add @better-webhook/ragie bash yarn add @better-webhook/ragie import { ragie } from "@better-webhook/ragie";
const webhook = ragie({ secret: process.env.RAGIE_WEBHOOK_SECRET }).event(
"document_status_updated",
async (payload) => {
console.log(`Document ${payload.document_id} is now ${payload.status}`);
},
);Ragie webhooks use an envelope structure where the event type is in
body.type and the actual payload is in body.payload. Ragie also includes a
body.nonce for idempotency — the SDK attaches this onto the unwrapped
payload as payload.nonce for convenience.
Events
document_status_updated
Triggered when a document enters indexed, keyword_indexed, ready, or failed state.
.event("document_status_updated", async (payload) => {
console.log(payload.document_id); // Document ID
console.log(payload.status); // "indexed", "ready", "failed", etc.
console.log(payload.name); // Document name
console.log(payload.external_id); // Your external ID (if provided)
console.log(payload.error); // Error message (if status is "failed")
})Key payload fields:
document_id— Unique document identifiernonce— Unique idempotency key for this webhook deliverystatus—indexed,keyword_indexed,ready, orfailedname— Document namepartition— Partition keymetadata— User-defined metadata (nullable)external_id— Your external ID (nullable)connection_id— Connection ID if created via connection (nullable)sync_id— Sync ID if part of a sync (nullable)error— Error message if status isfailed(nullable)
document_deleted
Triggered when a document is deleted.
.event("document_deleted", async (payload) => {
console.log(payload.document_id);
console.log(payload.name);
console.log(payload.external_id);
})Key payload fields:
document_id— Unique document identifiername— Document namepartition— Partition keymetadata— User-defined metadata (nullable)external_id— Your external ID (nullable)connection_id— Connection ID (nullable)sync_id— Sync ID (nullable)
entity_extracted
Triggered when entity extraction completes for a document.
.event("entity_extracted", async (payload) => {
console.log(payload.entity_id); // Extracted entity ID
console.log(payload.document_id); // Source document ID
console.log(payload.document_name); // Source document name
console.log(payload.instruction_id); // Extraction instruction ID
console.log(payload.data); // Extracted entity data
})Key payload fields:
entity_id— Unique identifier for the extracted entitydocument_id— Source document IDinstruction_id— Instruction ID used for extractiondocument_name— Source document namedocument_external_id— External ID of source documentdocument_metadata— Metadata from source documentpartition— Partition keysync_id— Sync ID (nullable)data— The extracted entity data object
connection_sync_started
Triggered when a connection sync begins.
.event("connection_sync_started", async (payload) => {
console.log(payload.connection_id);
console.log(payload.sync_id);
console.log(payload.partition);
console.log(`Will create ${payload.create_count} documents`);
console.log(`Will delete ${payload.delete_count} documents`);
})Key payload fields:
connection_id— Connection identifiersync_id— Sync identifierpartition— Partition keycreate_count— Number of documents to be createdupdate_content_count— Number of documents with content updatesupdate_metadata_count— Number of documents with metadata updatesdelete_count— Number of documents to be deleted
connection_sync_progress
Triggered periodically during a sync to report progress.
.event("connection_sync_progress", async (payload) => {
console.log(`Created: ${payload.created_count}/${payload.create_count}`);
console.log(`Deleted: ${payload.deleted_count}/${payload.delete_count}`);
console.log(`Errors: ${payload.errored_count}`);
})Key payload fields:
connection_id— Connection identifiersync_id— Sync identifierpartition— Partition keycreate_count/created_count— Total to create / created so farupdate_content_count/updated_content_count— Content updates total / completedupdate_metadata_count/updated_metadata_count— Metadata updates total / completeddelete_count/deleted_count— Total to delete / deleted so farerrored_count— Number of documents with errors
connection_sync_finished
Triggered when a connection sync completes.
.event("connection_sync_finished", async (payload) => {
console.log(`Sync ${payload.sync_id} finished`);
console.log(`Connection: ${payload.connection_id}`);
})Key payload fields:
connection_id— Connection identifiersync_id— Sync identifierpartition— Partition key
connection_limit_exceeded
Triggered when a connection exceeds its page limit.
.event("connection_limit_exceeded", async (payload) => {
console.log(`Connection ${payload.connection_id} hit ${payload.limit_type} limit`);
})Key payload fields:
connection_id— Connection identifierpartition— Partition keylimit_type— Type of limit exceeded (e.g.,"page_limit")
partition_limit_exceeded
Triggered when a partition exceeds its document limit.
.event("partition_limit_exceeded", async (payload) => {
console.log(`Partition ${payload.partition} hit limit`);
})Key payload fields:
partition— Partition keylimit_type— Type of limit exceeded (if provided)nonce— Unique idempotency key for this webhook delivery
Signature Verification
Ragie uses HMAC-SHA256 signatures sent in the X-Signature header.
Custom Providers
For webhook sources not covered by built-in providers, you can create custom providers using @better-webhook/core:
import { createProvider, createHmacVerifier, z } from "@better-webhook/core";
const OrderSchema = z.object({
orderId: z.string(),
status: z.enum(["pending", "completed", "cancelled"]),
amount: z.number(),
});
const myProvider = createProvider({
name: "my-ecommerce",
schemas: {
"order.created": OrderSchema,
"order.updated": OrderSchema,
},
getEventType: (headers) => headers["x-event-type"],
getDeliveryId: (headers) => headers["x-delivery-id"],
verify: createHmacVerifier({
algorithm: "sha256",
signatureHeader: "x-signature",
signaturePrefix: "sha256=",
}),
});The createHmacVerifier helper supports common signature formats. For custom
verification logic, provide your own verify function.
Envelope Payloads
Some webhook providers wrap the actual payload in an envelope structure. For example, a provider might send:
{
"type": "order.created",
"payload": { "orderId": "123", "status": "pending", "amount": 99.99 },
"timestamp": "2024-01-01T00:00:00Z"
}Use getEventType with the optional body parameter and getPayload to handle this:
const envelopeProvider = createProvider({
name: "my-envelope-provider",
schemas: {
"order.created": OrderSchema,
"order.updated": OrderSchema,
},
// Extract event type from body instead of headers
getEventType: (headers, body) => {
if (body && typeof body === "object" && "type" in body) {
return (body as { type: string }).type;
}
return undefined;
},
// Extract the actual payload from the envelope
getPayload: (body) => {
if (body && typeof body === "object" && "payload" in body) {
return (body as { payload: unknown }).payload;
}
return body;
},
verify: createHmacVerifier({
algorithm: "sha256",
signatureHeader: "x-signature",
}),
});With this configuration, your event handlers receive the unwrapped payload directly, and schema validation applies to the inner payload object.