BuildBaseBuildBase

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

FunctionWhat 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

CategoryEvents
Useruser.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
Organizationorganization.member_invited, organization.member_accepted, organization.member_role_changed, organization.member_removed
Workspaceworkspace.created, workspace.updated, workspace.deleted, workspace.settings_updated, workspace.features_updated, workspace.member_added, workspace.member_removed, workspace.member_role_changed
Subscriptionsubscription.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
Paymentpayment.succeeded, payment.failed, payment.action_required
Quotaquota.limit_exceeded
Creditcredit.purchased, credit.consumed, credit.expired, credit.granted, credit.revoked, credit.low_balance
Workflowworkflow.created, workflow.deleted, workflow.published, workflow.paused, workflow.resumed, workflow.instance_completed, workflow.instance_failed
Audienceaudience.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
Emailemail.domain_added, email.domain_verified, email.sent, email.opened, email.clicked, email.bounced, email.unsubscribed, email.campaign_sent
Pushpush.campaign_sent
Authauth.method_deleted, auth.domain_added, auth.domain_verified, auth.domain_deleted, auth.client_self_registered
Tokentoken.created, token.updated, token.revoked
Group_versiongroup_version.published
Formform.submitted

86+ 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:

HeaderContents
x-buildbase-signaturesha256=<hex> - HMAC-SHA256 of {timestamp}.{body} with your signing secret
x-buildbase-timestampSame Unix timestamp as the body
x-buildbase-eventThe event name (e.g. subscription.upgraded)

The data object varies by event type. Some common patterns:

EventKey data fields
user.registereduserId, email, name
subscription.createdworkspaceId, planId, billingInterval, trialEnd
subscription.trial_startedworkspaceId, subscriptionId, planVersionId, trialEnd
payment.failedworkspaceId, amount, currency, failureReason
credit.consumedworkspaceId, amount, description, remaining
workspace.member_addedworkspaceId, 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 event and timestamp, so deduplicate on a hash of the raw request body.
  • No ordering guarantee - events may arrive out of order. Use event.timestamp if ordering matters.
  • Automatic retries - a non-2xx response is retried 4 more times with exponential backoff, at roughly 10s, 20s, 40s and 80s, so the last attempt lands about 2.5 minutes after the first. A destination that is refused by our outbound safety checks is not retried at all; the delivery log says blocked.
  • Signature expiry - signatures are valid for 5 minutes by default. Reject older payloads to prevent replay attacks.

Limits

Webhooks fan out: one event reaching ten endpoints is ten calls to your servers, and some events fire per recipient rather than per action. These bounds keep one busy hour from becoming a queue nobody can drain.

LimitValueWhat happens at the limit
Endpoints per organization10 (excluding archived)Creating an eleventh is refused. Delete one first
Deliveries per minute600 per organizationFurther deliveries that minute are not sent, and appear in the delivery log as throttled
Consecutive failures50 per endpointThe endpoint is disabled automatically and shows Auto-disabled in the console. Any 2xx resets the count

A throttled delivery never left BuildBase, so it is not a failure of your receiver and is not retried. email.sent fires once per recipient, so a large campaign is the usual way to meet the per-minute limit; subscribe an endpoint to the specific events you need rather than to * if that matters to you.

Re-enabling an auto-disabled endpoint from the console clears its failure count.

Next Steps

  • Server SDK - Backend setup and all server methods.
  • Billing - Subscription events and payment handling.