WorkOS Integration
Receive and route WorkOS webhooks for Directory Sync, SSO connections and authentication events.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "WorkOS Production",
"slug": "workos",
"provider": "workos",
"signingSecret": "your-workos-signing-secret"
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/workos2. Configure the WorkOS Webhook
- Go to WorkOS Dashboard → Webhooks
- Click Create Endpoint and paste your Hookbase ingest URL
- Select the events you want to subscribe to
- Copy the endpoint's Signing Secret and store it on your Hookbase source as
signingSecret
Each endpoint has its own signing secret. See WorkOS's webhook documentation for the event catalog.
3. Create Destinations and Routes
# Create a destination
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Directory Sync Worker", "slug": "workos-dsync", "url": "https://api.myapp.com/webhooks/workos"}'
# Create a route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "WorkOS to Directory Sync", "sourceId": "src_...", "destinationId": "dst_..."}'Signature Verification
WorkOS signs a timestamp and the body together, not the body alone. The WorkOS-Signature header
carries both parts as comma-separated key=value pairs:
WorkOS-Signature: t=1700000000,v1=b7e1c9a3d5f70286c4a6e8d0f2b4a6c8e0d2f4a6b8c0d2e4f6a8b0c2d4e6f8a0v1 is the hex HMAC-SHA256 of the string {t}.{body} — the timestamp, a literal dot, then the raw
request body — keyed with the endpoint's signing secret.
Hookbase verifies this automatically once the source has both fields set:
{
"provider": "workos",
"signingSecret": "your-workos-signing-secret"
}Info
The timestamp is checked, not just signed. Hookbase rejects a WorkOS signature whose t is more
than 300 seconds away from the receiving clock. A correctly signed request that arrives outside
that window, or is replayed later, does not verify — which is what a signed timestamp is for.
Once events are arriving with signature_valid: true, set rejectInvalidSignatures: true on the
source to have unverified requests refused with 401 instead of stored.
Common Events
| Event | Description |
|---|---|
dsync.user.created | A directory user was created |
dsync.user.updated | A directory user was updated |
dsync.user.deleted | A directory user was deleted |
dsync.group.created | A directory group was created |
dsync.group.updated | A directory group was updated |
connection.activated | An SSO connection was activated |
connection.deactivated | An SSO connection was deactivated |
authentication.email_verification_succeeded | Email verification succeeded |
Directory User Created
{
"id": "event_01H8MWQR4XJ3FQKXRB3V2QY4N6",
"event": "dsync.user.created",
"created_at": "2024-01-15T10:30:00.000Z",
"data": {
"id": "directory_user_01H8MWQR4XJ3FQKXRB3V2QY4N6",
"directory_id": "directory_01H8MWQR4XJ3FQKXRB3V2QY4N6",
"first_name": "Jane",
"last_name": "Developer",
"emails": [
{
"type": "work",
"value": "jane@example.com",
"primary": true
}
],
"state": "active"
}
}Connection Activated
{
"id": "event_02H9NXRS5YK4GRLYSCAT3RZ5O7",
"event": "connection.activated",
"created_at": "2024-01-15T11:00:00.000Z",
"data": {
"id": "conn_01H8MWQR4XJ3FQKXRB3V2QY4N6",
"connection_type": "OktaSAML",
"name": "Acme Inc SSO",
"state": "active",
"organization_id": "org_01H8MWQR4XJ3FQKXRB3V2QY4N6"
}
}Transform Examples
A javascript transform receives the parsed payload and returns the body Hookbase delivers.
Provisioning Row
function transform(payload) {
const d = payload.data || {};
const primary = (d.emails || []).find(e => e.primary);
return {
event_id: payload.id,
event: payload.event,
directory_user_id: d.id || null,
directory_id: d.directory_id || null,
email: primary ? primary.value : null,
first_name: d.first_name || null,
last_name: d.last_name || null,
state: d.state || null,
occurred_at: payload.created_at
};
}Slack Alert on SSO Connection Changes
function transform(payload) {
const d = payload.data || {};
return {
text: `WorkOS connection ${d.state}: ${d.name}`,
blocks: [
{
type: "section",
fields: [
{ type: "mrkdwn", text: `*Connection:*\n${d.name || d.id}` },
{ type: "mrkdwn", text: `*Type:*\n${d.connection_type || "unknown"}` },
{ type: "mrkdwn", text: `*State:*\n${d.state || "unknown"}` },
{ type: "mrkdwn", text: `*Organization:*\n${d.organization_id || "unknown"}` }
]
}
]
};
}Filter Examples
Filter conditions read dotted paths out of the payload. logic must be AND or OR.
Directory Sync Events Only
{
"name": "Directory Sync",
"logic": "AND",
"conditions": [
{
"field": "event",
"operator": "starts_with",
"value": "dsync."
}
]
}Deprovisioning Only
{
"name": "Deprovisioning",
"logic": "OR",
"conditions": [
{
"field": "event",
"operator": "equals",
"value": "dsync.user.deleted"
},
{
"field": "event",
"operator": "equals",
"value": "connection.deactivated"
}
]
}One Directory
{
"name": "Acme Directory",
"logic": "AND",
"conditions": [
{
"field": "data.directory_id",
"operator": "equals",
"value": "directory_01H8MWQR4XJ3FQKXRB3V2QY4N6"
}
]
}Headers
| Header | Description |
|---|---|
WorkOS-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256 of "{t}.{body}"> |
Hookbase does not forward these headers to your destination. A delivery is a fresh request:
Hookbase sets Content-Type, User-Agent: Hookbase/1.0, X-Delivery-ID and X-Event-ID, then
adds the headers and auth you configured on the destination. Nothing WorkOS sent reaches your
handler as a header — read what you need out of the body, or set it on the destination yourself.
Of the incoming headers, only content-type, user-agent, x-github-event, x-gitlab-event and
stripe-signature are stored on the event, so those are the only ones visible later in the
dashboard or the API.
Troubleshooting
Signature Verification Failed
- Confirm the source's
providerisworkos— a source left oncustomreadsX-Signature,X-Webhook-SignatureandX-Hub-Signature-256, and WorkOS sendsWorkOS-Signature - Confirm
signingSecretis that endpoint's signing secret. An account with several endpoints has several secrets, and they are not interchangeable - Check the clock: the signed timestamp must be within 300 seconds. A delivery replayed from a capture will not verify even though the signature is genuine
Missing Events
- Check the endpoint's event subscriptions in Dashboard → Webhooks
- Directory Sync events only fire for directories that are connected and syncing
- Check the endpoint is enabled and pointed at the right ingest URL
Duplicate Events
WorkOS puts a unique id on every event. Hookbase has no provider event-id extractor for WorkOS, so
the default auto dedup strategy falls back to hashing the payload — which is equivalent here,
because id is part of the body. Use payload.id in your handler as the idempotency key.