Webhooks

Receive real-time notifications when subscription events occur in your app.

How Webhooks Work

When events happen in StackBE (new subscription, payment failed, etc.), we send an HTTP POST request to your configured webhook URL with event details.

  1. Configure your webhook URL in the StackBE dashboard
  2. StackBE sends a signed POST request when events occur
  3. Your server verifies the signature and processes the event
  4. Return a 2xx status to acknowledge receipt

Configuration

Set up your webhook endpoint in the StackBE dashboard:

  1. Go to app.stackbe.io → Your App → Settings → Dev
  2. Enter your Webhook URL (e.g., https://yourapp.com/api/webhooks/stackbe)
  3. Copy your Webhook Signing Secret
  4. Click "Save Webhook Settings"

Important: Your webhook endpoint must be publicly accessible and respond within 30 seconds.

Event Types

Subscription Events

  • subscription.created — New subscription activated or trial started
  • subscription.updated — Plan changed, status changed, or trial ended
  • subscription.canceled — Subscription was canceled
  • subscription.paused — Subscription was paused
  • subscription.resumed — Subscription was resumed

Customer Events

  • customer.created — New customer record created
  • customer.updated — Customer details changed

Payment Events

  • payment.succeeded — Payment was successful
  • payment.failed — Payment failed (triggers dunning)

Entitlement Events

  • entitlements.updated — Customer's feature access changed

Webhook Payload

All webhook payloads follow this structure:

json
{
  "id": "evt_abc123",
  "type": "subscription.created",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "subscription": {
      "id": "sub_xyz789",
      "customerId": "cust_abc123",
      "planId": "plan_pro",
      "status": "active",
      "currentPeriodEnd": "2025-02-15T10:30:00Z"
    },
    "customer": {
      "id": "cust_abc123",
      "email": "user@example.com"
    },
    "plan": {
      "id": "plan_pro",
      "name": "Pro Plan",
      "entitlements": {
        "api_calls": 10000,
        "premium_support": true
      }
    }
  }
}

Signature Verification

Always verify webhook signatures to ensure requests are from StackBE. The signature is in the x-stackbe-signature header.

Using the SDK (Recommended)

typescript
import { StackBE } from '@stackbe/sdk';

const stackbe = new StackBE({
  apiKey: process.env.STACKBE_API_KEY!,
  appId: process.env.STACKBE_APP_ID!,
});

// Next.js App Router
export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('x-stackbe-signature')!;

  // Verify and parse the webhook
  const event = stackbe.webhooks.verify(body, signature, {
    secret: process.env.STACKBE_WEBHOOK_SECRET!,
  });

  // Handle the event
  switch (event.type) {
    case 'subscription.created':
      const { subscription, customer } = event.data;
      console.log(`New subscription for ${customer.email}`);
      // Update your database, send welcome email, etc.
      break;

    case 'subscription.canceled':
      // Handle cancellation
      break;

    case 'payment.failed':
      // Handle failed payment (send dunning email, etc.)
      break;
  }

  return new Response('OK', { status: 200 });
}

Manual Verification

If you're not using the SDK, verify the HMAC-SHA256 signature manually:

typescript
import crypto from 'crypto';

function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your handler
const isValid = verifyWebhook(rawBody, signature, process.env.STACKBE_WEBHOOK_SECRET!);
if (!isValid) {
  return new Response('Invalid signature', { status: 401 });
}

Best Practices

  • Always verify signatures — Never process webhooks without validating the signature first.
  • Return 2xx quickly — Acknowledge receipt immediately, then process asynchronously if needed.
  • Handle duplicates — Webhooks may be retried. Use the event id for idempotency.
  • Use a queue for heavy processing — Offload work to a background job queue to avoid timeouts.
  • Log webhook payloads — Store raw payloads for debugging and replay.

Retry Policy

If your endpoint returns a non-2xx status or times out, StackBE retries with exponential backoff:

  • 1st retry: 1 minute after initial attempt
  • 2nd retry: 5 minutes
  • 3rd retry: 30 minutes
  • 4th retry: 2 hours
  • 5th retry: 24 hours

After 5 failed attempts, the webhook is marked as failed. You can view failed webhooks and trigger manual retries in the dashboard.

Testing Webhooks

Test your webhook endpoint from the dashboard:

  1. Go to Settings → Dev → Webhooks
  2. Click "Send Test Event"
  3. Select an event type
  4. Check the response status and your logs

For local development, use a tunneling tool like ngrok to expose your local server.

Next Steps