Webhooks
Webhooks let external services notify your Sixb project when something changes.
Define a webhook
Add webhooks to a connector adapter in connectors/. Use defineWebhook() to validate the
payload, verify the sender, and handle the event.
This example receives a CRM event, fetches the current person, and updates a dataset:
import { change, defineConnector, defineWebhook } from "@sixb/core"
import { people } from "../datasets/people"
import { createCrmClient, personEventSchema, verifyCrmSignature } from "../lib/crm"
export const crm = defineConnector("crm", {
type: "crm",
connect: () => createCrmClient(),
webhooks: [
defineWebhook("person-updated")
.post()
.json(personEventSchema)
.verify(({ request, rawBody }) => verifyCrmSignature(request, rawBody))
.idempotencyKey(({ body }) => body.deliveryId)
.handle(async ({ body, client, sixb }) => {
const crm = await client()
const person = await crm.getPerson(body.personId)
await sixb.datasets.ingest(people, {
changes: [change.upsert({
id: String(person.id),
name: person.name,
updatedAt: person.updatedAt,
})],
})
}),
],
})
The ../lib/crm imports are your provider-specific code: a client factory, a schema with
parse(unknown) returning { personId, deliveryId }, and a signature verifier that throws when
verification fails. Verify signatures using the provider's documented method and the original
rawBody bytes.
The example uses the people dataset. See
ingesting changes for dataset updates and deletes.
ingest returns once the changes are stored, before they are in the dataset. A handler that
needs the data afterwards triggers that work from the new version instead: see
react to new data.
Register the URL
Register your webhook's URL with the external service:
https://<sixb-api-origin>/api/webhooks/crm/person-updated
The last two segments are the connector ID and webhook ID. Sixb registers the route automatically
and returns 202 Accepted when the handler completes without an explicit response. Return
{ status: 200 } from the handler if your provider requires it.
Providers may retry deliveries. Use a stable delivery ID with .idempotencyKey() to deduplicate
requests, and make handler writes safe to repeat. A failed handler can have already completed
some of its writes.
With a delivery ID, a retry that arrives while the handler still runs gets 503 Service Unavailable
with Retry-After and runs nothing, however long the handler takes. The provider tries again, so a
handler that then fails does not lose the delivery. If the process running it dies, retries run the
handler again from 60 seconds after that. A provider that does not retry loses that delivery.
OAuth accounts
For OAuth connectors, resolve the account named in the event before accessing its client. Inside your webhook handler:
.handle<ProviderClient>(async ({ body, connections }) => {
for (const account of await connections.forAccount(body.accountId)) {
const client = await account.client()
await handleProviderEvent(client, body)
}
})
ProviderClient is your adapter's client type, and handleProviderEvent is your event handler.
connections.forAccount() returns all matching connected accounts, or an empty array when none
match. The top-level client() used in the first example is for connectors without managed OAuth.