Getting Started
This guide walks you through integrating Hookbase into your application to provide webhooks to your customers.
Overview
Hookbase provides two main packages for integration:
- @hookbase/sdk - Server-side SDK for sending webhooks and managing resources
- @hookbase/portal - Embeddable React components for customer-facing webhook management
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your App │────▶│ Hookbase │────▶│ Customer's │
│ (Backend) │ │ API │ │ Endpoint │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Your App │ │ Hookbase │
│ (Frontend) │────▶│ Portal │
└─────────────────┘ └─────────────────┘Try it in one click
The fastest way to see an outbound webhook end to end is the dashboard quickstart. No code, no URL of your own needed.
- Open Outbound Webhooks in the dashboard. A new organization sees a Send your first webhook card (it is also offered in the outbound empty states and as the first option of the onboarding wizard's "Send Webhooks" path).
- Click Send your first webhook. Hookbase:
- creates a Quickstart demo application (external ID
hookbase-quickstart), - creates an
order.createdevent type with a realistic example payload, - creates a request bin and an endpoint that delivers to it,
- subscribes the endpoint to
order.created, - sends one
order.createdevent through the normal delivery pipeline, signed like every other delivery.
- creates a Quickstart demo application (external ID
- The card shows what was created, with links to the application, endpoint and message, and the request as the bin captured it: the
webhook-id,webhook-timestampandwebhook-signatureheaders and the body. Watch it arrive opens the bin's live view.
Running it again reuses the same application, event type, endpoint and subscription, so it creates no duplicates. It counts toward your plan's limits like any other resource; if a limit is reached, the card shows the reason.
Next steps from the quickstart
- Point it at your own URL: opens the endpoint editor so you can swap the bin URL for your own receiver. The endpoint keeps its subscription and signing secret.
- Send the next event from your backend: the card has a copyable
curlcommand with your application ID and event type filled in. Create an API key under Settings → API keys and use it in place ofYOUR_API_KEY:
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/send-event \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"eventType": "order.created",
"applicationId": "{applicationId}",
"payload": { "orderId": "ord_1002", "amount": 2500, "currency": "usd" }
}'- Example payloads: an event type's example payload (set in the event type form) pre-fills the dashboard's Send tab when you pick that event type. Text you have already typed is never replaced; Use example payload puts the example back.
The quickstart is also available from the API: POST /api/organizations/{orgId}/outbound/quickstart. All body fields are optional: endpointUrl (deliver to your URL instead of a new bin), eventTypeName (default order.created) and applicationName (default Quickstart demo).
The step-by-step wizard
If you would rather build each piece yourself, choose Set it up step by step in the onboarding wizard. It creates an event type, an application and an endpoint, subscribes the endpoint to the event type for you, and ends with Send a test event, which sends the event type's example payload and shows the delivery result.
Quick Start
1. Install the SDK
npm install @hookbase/sdk2. Initialize the Client
import { Hookbase } from '@hookbase/sdk';
const hookbase = new Hookbase({
apiKey: process.env.HOOKBASE_API_KEY,
});3. Create an Application for Your Customer
// When a new customer signs up
const app = await hookbase.applications.create({
name: customer.name,
uid: customer.id, // Your internal customer ID
metadata: {
plan: customer.plan,
email: customer.email,
},
});4. Define Event Types
// Set up the event types your customers can subscribe to
await hookbase.eventTypes.create({
name: 'order.created',
displayName: 'Order Created',
description: 'Triggered when a new order is placed',
category: 'Orders',
});
await hookbase.eventTypes.create({
name: 'order.shipped',
displayName: 'Order Shipped',
description: 'Triggered when an order ships',
category: 'Orders',
});
await hookbase.eventTypes.create({
name: 'payment.completed',
displayName: 'Payment Completed',
description: 'Triggered when payment is received',
category: 'Payments',
});5. Send Webhooks
// When an event occurs in your application
await hookbase.messages.send(customer.hookbaseAppId, {
eventType: 'order.created',
payload: {
orderId: order.id,
amount: order.total,
currency: order.currency,
items: order.items,
customer: {
id: order.customerId,
email: order.customerEmail,
},
},
eventId: `order_created_${order.id}`, // For deduplication
});Embed the Portal
1. Install the Portal Package
npm install @hookbase/portal2. Generate a Portal Token
// Backend API endpoint
app.get('/api/webhook-portal-token', async (req, res) => {
const customer = await getCustomerFromSession(req);
const token = await hookbase.portalTokens.create(customer.hookbaseAppId, {
scopes: ['read', 'write'],
expiresInDays: 1, // 1 to 365; the default is 30
});
res.json({ token: token.token });
});3. Embed the Portal
import { useState, useEffect } from 'react';
import {
HookbasePortal,
EndpointList,
EndpointForm,
SubscriptionManager,
MessageLog,
} from '@hookbase/portal';
import '@hookbase/portal/styles.css';
function WebhookSettings() {
const [portalToken, setPortalToken] = useState<string | null>(null);
const [selectedEndpoint, setSelectedEndpoint] = useState(null);
useEffect(() => {
fetch('/api/webhook-portal-token')
.then((res) => res.json())
.then((data) => setPortalToken(data.token));
}, []);
if (!portalToken) return <div>Loading...</div>;
return (
<HookbasePortal token={portalToken}>
<div className="grid grid-cols-2 gap-6">
<div>
<h2 className="text-xl font-semibold mb-4">Endpoints</h2>
<EndpointForm
onSuccess={(endpoint, secret) => {
// Show the secret to the user - it won't be available again
alert(`Save this secret: ${secret}`);
}}
/>
<EndpointList
onEndpointClick={setSelectedEndpoint}
/>
</div>
<div>
{selectedEndpoint ? (
<>
<h2 className="text-xl font-semibold mb-4">Subscriptions</h2>
<SubscriptionManager endpointId={selectedEndpoint.id} />
</>
) : (
<p>Select an endpoint to manage subscriptions</p>
)}
</div>
</div>
<div className="mt-8">
<h2 className="text-xl font-semibold mb-4">Delivery History</h2>
<MessageLog
limit={25}
refreshInterval={30000}
/>
</div>
</HookbasePortal>
);
}Verify Incoming Webhooks
When your customers receive webhooks, they need to verify the signature.
Share This Code with Your Customers
import { createWebhook } from '@hookbase/sdk';
// Create a verifier with the endpoint's signing secret
const webhook = createWebhook(process.env.WEBHOOK_SECRET);
// Express.js example
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const payload = webhook.verify(req.body.toString(), req.headers as Record<string, string>);
// The body is the payload you sent; the event type arrives in a header
switch (req.headers['x-hookbase-event-type']) {
case 'order.created':
handleOrderCreated(payload);
break;
case 'payment.completed':
handlePaymentCompleted(payload);
break;
}
res.status(200).send('OK');
} catch (error) {
console.error('Webhook verification failed:', error);
res.status(401).send('Invalid signature');
}
});Best Practices
1. Use Idempotency Keys
Pass an idempotencyKey derived from your own record, so a retried send (after a timeout, or a redeploy mid-request) returns the first send's result instead of delivering the event twice:
await hookbase.messages.send(appId, {
eventType: 'order.created',
payload: orderData,
idempotencyKey: `order_created_${order.id}`,
});Without one, the SDK generates a key per send call, which covers its own retries but not a second call from your code.
2. Use Application UIDs
Map your internal customer IDs to Hookbase applications:
// Store the Hookbase app ID in your database
const app = await hookbase.applications.getOrCreate(customer.id, {
name: customer.name,
});
// Later, use the UID to find the app
const app = await hookbase.applications.getByUid(customer.id);3. Handle Delivery Failures
Monitor failed deliveries and alert your customers:
// List failed messages
const { data: failures } = await hookbase.messages.listAllOutbound(appId, {
status: 'exhausted',
});
// Notify customer about failures
for (const failure of failures) {
await notifyCustomer(appId, {
message: `Webhook delivery failed to ${failure.endpointUrl}`,
eventType: failure.eventType,
error: failure.lastError,
});
}4. Implement Retry Logic
When your customers' endpoints fail, Hookbase automatically retries with exponential backoff. You can also manually retry:
// Retry a specific failed delivery
await hookbase.messages.retry(appId, outboundMessageId);
// Resend to all endpoints
await hookbase.messages.resend(appId, messageId);5. Use Event Categories
Organize your event types into categories for better UX:
await hookbase.eventTypes.create({
name: 'order.created',
category: 'Orders',
});
await hookbase.eventTypes.create({
name: 'order.shipped',
category: 'Orders',
});
await hookbase.eventTypes.create({
name: 'invoice.created',
category: 'Billing',
});Webhook Payload Format
The request body is the payload you sent, serialized as JSON, with nothing wrapped around it. For messages.send(appId, { eventType: 'order.created', payload: { orderId: 'ord_123', amount: 99.99, currency: 'USD' } }) the endpoint receives:
{"orderId":"ord_123","amount":99.99,"currency":"USD"}(A subscription transform can reshape the body per endpoint; the signature always covers the body as delivered.)
Headers
| Header | Description |
|---|---|
webhook-id | The message ID (a UUID). It is the same on every retry, so use it to deduplicate |
webhook-timestamp | Unix timestamp (seconds) when this attempt was signed |
webhook-signature | v1,<base64> (HMAC-SHA256) or v1a,<base64> (Ed25519); two entries during a secret rotation. See Signature Verification |
x-hookbase-event-type | The event type, e.g. order.created |
Content-Type | application/json |
Rate Limits
| Resource | Limit |
|---|---|
| API Requests | 1000/minute |
| Webhook Sends | 10000/minute |
| Portal Tokens | 100/hour per application |
Rate limit headers are included in all API responses:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
Support
- Documentation: https://hookbase.app/docs
- API Reference: https://hookbase.app/docs/api
- GitHub: https://github.com/hookbase/hookbase
- Email: support@hookbase.app