Overview
Receive real-time event notifications when users sign up, subscribe, pay, or trigger actions.
BuildBase fires webhook events for every significant action — user signups, subscription changes, payments, credit usage, workspace updates, and more. Verify the signature and handle the event in your backend.
import { parseWebhookEvent } from '@buildbase/sdk';
export async function POST(req) {
const body = await req.text();
const event = parseWebhookEvent({
body,
signature: req.headers.get('x-buildbase-signature'),
timestamp: req.headers.get('x-buildbase-timestamp'),
secret: process.env.WEBHOOK_SECRET,
});
if (!event) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
switch (event.event) {
case 'subscription.upgraded':
await enablePremiumFeatures(event.data.workspaceId);
break;
case 'credit.low_balance':
await notifyTeam(event.data.workspaceId);
break;
case 'workspace.member_added':
await syncToExternalCRM(event.data);
break;
}
return Response.json({ received: true });
}Before you start
Create a webhook endpoint in the BuildBase dashboard and copy the signing secret.
Two functions
| Function | What it does |
|---|---|
verifyWebhookSignature({ body, signature, timestamp, secret }) | Returns true if the signature is valid |
parseWebhookEvent({ body, signature, timestamp, secret }) | Verifies and parses — returns the event object or null |
Use parseWebhookEvent for most cases. Use verifyWebhookSignature if you want to parse the body yourself.
Express / Hono example
import { parseWebhookEvent } from '@buildbase/sdk';
app.post('/webhooks', (req, res) => {
const event = parseWebhookEvent({
body: req.body,
signature: req.headers['x-buildbase-signature'],
timestamp: req.headers['x-buildbase-timestamp'],
secret: process.env.WEBHOOK_SECRET,
});
if (!event) return res.status(401).json({ error: 'Invalid' });
// Handle event.event and event.data
res.json({ received: true });
});Event categories
| Category | Events |
|---|---|
| User | user.registered, user.logged_in, user.new_device_login, user.email_verified, user.password_changed, user.password_reset_requested, user.profile_updated, user.tag_added, user.tag_removed, user.blocked, user.unblocked |
| Organization | organization.member_invited, organization.member_accepted, organization.member_removed |
| Workspace | workspace.created, workspace.updated, workspace.deleted, workspace.settings_updated, workspace.features_updated, workspace.member_added, workspace.member_removed, workspace.member_role_changed |
| Subscription | subscription.created, subscription.updated, subscription.upgraded, subscription.canceled, subscription.cancel_scheduled, subscription.resumed, subscription.suspended, subscription.trial_started, subscription.trial_will_end, subscription.trial_expired, subscription.downgraded |
| Payment | payment.succeeded, payment.failed, payment.action_required |
| Quota | quota.limit_exceeded |
| Credit | credit.purchased, credit.consumed, credit.expired, credit.granted, credit.revoked, credit.low_balance |
| Workflow | workflow.created, workflow.deleted, workflow.published, workflow.paused, workflow.resumed, workflow.instance_completed, workflow.instance_failed |
| Audience | audience.member_created, audience.member_updated, audience.added_to_list, audience.removed_from_list, audience.attribute_changed, audience.tag_added, audience.tag_removed, audience.unsubscribed, audience.resubscribed, audience.email_invalidated, audience.blocked, audience.unblocked, audience.import_completed, audience.list_created, audience.list_updated, audience.list_deleted |
email.domain_added, email.domain_verified, email.sent, email.opened, email.clicked, email.bounced, email.unsubscribed, email.campaign_sent | |
| Push | push.campaign_sent |
| Auth | auth.method_deleted, auth.domain_added, auth.domain_verified, auth.domain_deleted, auth.client_self_registered |
| Token | token.created, token.revoked |
| Group_version | group_version.published |
| Form | form.submitted |
84+ event types total. See the SDK README for the full list.
Event payload structure
Every webhook POST body has this shape — three fields, nothing else:
{
"event": "subscription.upgraded",
"timestamp": 1719000000,
"data": {
"workspaceId": "ws_xyz789",
"userId": "usr_def456",
"planId": "plan_starter",
"planVersionId": "pv_v2",
"billingInterval": "monthly",
"previousPlanId": "plan_free"
}
}timestamp is Unix seconds, set when the event fires. Each delivery also carries three headers:
| Header | Contents |
|---|---|
x-buildbase-signature | sha256=<hex> — HMAC-SHA256 of {timestamp}.{body} with your signing secret |
x-buildbase-timestamp | Same Unix timestamp as the body |
x-buildbase-event | The event name (e.g. subscription.upgraded) |
The data object varies by event type. Some common patterns:
| Event | Key data fields |
|---|---|
user.registered | userId, email, name |
subscription.created | workspaceId, planId, billingInterval, trialEnd |
payment.failed | workspaceId, amount, currency, failureReason |
credit.consumed | workspaceId, amount, description, remaining |
workspace.member_added | workspaceId, userId, role |
Delivery guarantees
- At-least-once delivery — events may be delivered more than once. Deliveries carry no unique event ID; retries repeat the same
eventandtimestamp, so deduplicate on a hash of the raw request body. - No ordering guarantee — events may arrive out of order. Use
event.timestampif ordering matters. - Automatic retries — failed deliveries (non-2xx response) are retried with backoff.
- Signature expiry — signatures are valid for 5 minutes by default. Reject older payloads to prevent replay attacks.
Next Steps
- Server SDK — Backend setup and all server methods.
- Billing — Subscription events and payment handling.