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.
- Configure your webhook URL in the StackBE dashboard
- StackBE sends a signed POST request when events occur
- Your server verifies the signature and processes the event
- Return a 2xx status to acknowledge receipt
Configuration
Set up your webhook endpoint in the StackBE dashboard:
- Go to app.stackbe.io → Your App → Settings → Dev
- Enter your Webhook URL (e.g.,
https://yourapp.com/api/webhooks/stackbe) - Copy your Webhook Signing Secret
- 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 startedsubscription.updated— Plan changed, status changed, or trial endedsubscription.canceled— Subscription was canceledsubscription.paused— Subscription was pausedsubscription.resumed— Subscription was resumed
Customer Events
customer.created— New customer record createdcustomer.updated— Customer details changed
Payment Events
payment.succeeded— Payment was successfulpayment.failed— Payment failed (triggers dunning)
Entitlement Events
entitlements.updated— Customer's feature access changed
Webhook Payload
All webhook payloads follow this structure:
{
"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)
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:
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
idfor 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:
- Go to Settings → Dev → Webhooks
- Click "Send Test Event"
- Select an event type
- Check the response status and your logs
For local development, use a tunneling tool like ngrok to expose your local server.