@hookbase/sdk
The Hookbase SDK is a type-safe client for the Hookbase API. Use it to send webhooks to your customers, manage the resources behind them, and verify webhook signatures.
Other Languages
This page covers the Node.js SDK. Official SDKs are available for these languages:
| Language | Install | Reference |
|---|---|---|
| Node.js / TypeScript (Node 18+) | npm install @hookbase/sdk | This page |
| Python (3.9+) | pip install hookbase | Python SDK reference |
| Java (25+) | Gradle: implementation("app.hookbase:hookbase-java:1.1.0") | Java SDK reference |
| Go (1.21+) | go get github.com/HookbaseApp/hookbase-go | Go SDK reference |
| .NET (8+) | dotnet add package Hookbase | .NET SDK reference |
Installation
npm install @hookbase/sdkThe package requires Node.js 18 or later and ships both ES module and CommonJS builds.
Quick Start
import { Hookbase } from '@hookbase/sdk';
const hookbase = new Hookbase({
apiKey: process.env.HOOKBASE_API_KEY!,
});
// Send a webhook
await hookbase.messages.send(appId, {
eventType: 'order.created',
payload: {
orderId: 'ord_123',
amount: 99.99,
},
});Client Configuration
import { Hookbase, createClient } from '@hookbase/sdk';
const hookbase = new Hookbase({
apiKey: process.env.HOOKBASE_API_KEY!, // A whr_ API key
baseUrl: 'https://api.hookbase.app', // Optional, defaults to production
timeout: 30000, // Request timeout in ms (default 30000)
retries: 3, // Retry attempts for retryable failures (default 3)
debug: false, // Log requests to the console
});
// Equivalent factory function
const client = createClient({ apiKey: process.env.HOOKBASE_API_KEY! });Retries
The client retries a request only when it is safe to send it again:
- 429 Too Many Requests is retried for every method, after the
Retry-Afterdelay (seconds or an HTTP date; 60 seconds when the header is missing). ARetry-Afterlonger than 60 seconds, such as a quota window, is not waited out:HookbaseRateLimitErroris thrown at once withretryAfterset. - 5xx responses, timeouts and network errors are retried with exponential backoff and jitter for
GET,PUT,PATCHandDELETE. APOSTis retried on these only when it carries an idempotency key (passingretriesalone is not enough), because the first attempt may already have run. APOSTwhose connection was refused is always retried, because it never reached the server. - Other 4xx responses are never retried.
messages.send always sends an idempotency key: yours if you pass one, otherwise one it generates for that call. Its retries therefore never send the event twice.
Per-request options
Every method takes an optional last RequestOptions argument:
const controller = new AbortController();
await hookbase.messages.send(
appId,
{ eventType: 'order.created', payload: { orderId: 'ord_123' } },
{
timeout: 60000, // Override the client timeout
retries: 5, // Override the retry count
idempotencyKey: 'order_created_ord_123', // Makes a POST safe to retry
signal: controller.signal, // Cancel the request (a cancelled request is not retried)
}
);Applications
Applications represent your customers. Each customer should have one application.
Create Application
const app = await hookbase.applications.create({
name: 'Acme Corp',
uid: 'customer_123', // Your internal customer ID, stored as externalId
metadata: {
plan: 'enterprise',
email: 'admin@acme.com',
},
});Get or Create Application
// Returns the existing app for this external ID, or creates it
const app = await hookbase.applications.getOrCreate('customer_123', {
name: 'Acme Corp',
});List Applications
Outbound lists are cursor-paginated. Pass the returned cursor back to get the next page.
const page = await hookbase.applications.list({ limit: 50 });
console.log(page.data, page.hasMore);
if (page.cursor) {
const next = await hookbase.applications.list({ limit: 50, cursor: page.cursor });
}
// Or let the async iterator follow the cursor for you
for await (const app of hookbase.applications.listAll()) {
console.log(app.name);
}Get by External ID
const app = await hookbase.applications.getByUid('customer_123');
console.log(app.externalId); // 'customer_123'Event Types
Event types define the webhook events your customers can subscribe to.
Create Event Type
await hookbase.eventTypes.create({
name: 'order.created',
displayName: 'Order Created',
description: 'Triggered when a new order is placed',
category: 'Orders',
schema: {
type: 'object',
properties: {
orderId: { type: 'string' },
amount: { type: 'number' },
},
},
});List and Find Event Types
const { data: eventTypes } = await hookbase.eventTypes.list({
category: 'Orders',
});
const eventType = await hookbase.eventTypes.getByName('order.created');
// Returns the existing event type, or creates it
await hookbase.eventTypes.getOrCreate('order.shipped', { displayName: 'Order Shipped' });Messages
Messages are the webhooks you send to your customers.
Send Message
const result = await hookbase.messages.send(appId, {
eventType: 'order.created',
payload: {
orderId: 'ord_123',
amount: 99.99,
currency: 'USD',
},
idempotencyKey: `order_created_${order.id}`, // A repeated key returns the first send's result
labels: { region: 'eu' }, // Subscriptions can filter on labels
metadata: {
source: 'api',
},
});
console.log(result.eventId); // The outbound event
console.log(result.messagesQueued); // One message per targeted endpoint
console.log(result.endpoints); // [{ id, url }]To deliver to specific endpoints only, pass endpointIds. Otherwise the event goes to every endpoint subscribed to its event type.
List Messages
const { data: messages } = await hookbase.messages.list(appId, {
eventType: 'order.created',
status: 'failed',
});Delivery Attempts
const attempts = await hookbase.messages.listAttempts(appId, messageId);Replay a Message
// Replays the message as a new outbound message
const replay = await hookbase.messages.retry(appId, messageId);
console.log(replay.newMessageId);Endpoints
Endpoints are managed by your customers through the Portal, but you can also manage them programmatically.
Create Endpoint
const endpoint = await hookbase.endpoints.create(appId, {
url: 'https://example.com/webhooks',
description: 'Production endpoint',
});
console.log(endpoint.secret); // whsec_..., shown once
// Ed25519 (Standard Webhooks v1a): Hookbase keeps the private key, you get the public key
const ed = await hookbase.endpoints.create(appId, {
url: 'https://example.com/webhooks',
signatureScheme: 'ed25519',
});
console.log(ed.publicKey); // whpk_...List Endpoints
const { data: endpoints } = await hookbase.endpoints.list(appId);Rotate the Signing Secret
// The previous key keeps signing for the grace period (default 3600 seconds)
const rotated = await hookbase.endpoints.rotateSecret(appId, endpointId, {
gracePeriodSeconds: 600,
});
if (rotated.signatureScheme === 'ed25519') {
console.log(rotated.publicKey);
} else {
console.log(rotated.secret);
}Send a Test Event
const test = await hookbase.endpoints.test(appId, endpointId, {
eventType: 'order.created',
payload: { orderId: 'test_123' },
});
console.log(test.success, test.delivery.responseStatus);Get Endpoint Statistics
const stats = await hookbase.endpoints.getStats(appId, endpointId);
// { totalMessages, totalSuccesses, totalFailures, successRate (percent), averageLatency (ms), recentFailures }Subscriptions
Subscriptions connect endpoints to event types, with optional filters.
await hookbase.subscriptions.create(appId, {
endpointId,
eventTypeId,
labelFilters: { region: 'eu' },
filterExpression: 'amount > 100', // JSONata, evaluated on the payload
});
// Subscribe one endpoint to several event types at once
await hookbase.subscriptions.subscribeToMany(appId, endpointId, [eventTypeId1, eventTypeId2]);Portal Tokens
Portal tokens authenticate the embedded Portal component.
Create Portal Token
const token = await hookbase.portalTokens.create(appId, {
expiresInDays: 1, // 1-365, default 30
scopes: ['read', 'write'],
});
// Return token.token to your frontendWebhook Verification
Help your customers verify incoming webhooks. Verification follows Standard Webhooks: it checks the webhook-id, webhook-timestamp and webhook-signature headers and rejects timestamps more than 5 minutes from now.
Create Verifier
import { createWebhook } from '@hookbase/sdk';
const webhook = createWebhook(process.env.WEBHOOK_SECRET!); // whsec_...
// Verify and parse the webhook
const payload = webhook.verify(rawBody, headers);rawBody is the exact request body, as a string or a Buffer/Uint8Array. Do not parse and re-serialize it first. headers can be a plain object, Node's req.headers, or a Fetch API Headers object.
import express from 'express';
import { createWebhook, HookbaseWebhookVerificationError } from '@hookbase/sdk';
const app = express();
const webhook = createWebhook(process.env.WEBHOOK_SECRET!);
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = webhook.verify<{ type: string }>(req.body, req.headers);
console.log('Event:', event.type);
res.sendStatus(200);
} catch (error) {
if (error instanceof HookbaseWebhookVerificationError) {
return res.status(401).send('Invalid signature');
}
throw error;
}
});Ed25519 Endpoints
For an endpoint created with signatureScheme: 'ed25519', pass its public key (endpoint.publicKey, whpk_...) instead of a secret. The key's prefix selects the scheme. During a key rotation the header carries both signatures, and either one is accepted.
import { createWebhook } from '@hookbase/sdk';
const webhook = createWebhook(process.env.HOOKBASE_PUBLIC_KEY!); // whpk_...
webhook.scheme; // 'ed25519'
const payload = webhook.verify(rawBody, headers);Options and Signature-Only Checks
import { createWebhook } from '@hookbase/sdk';
const webhook = createWebhook(process.env.WEBHOOK_SECRET!);
// Widen or narrow the timestamp tolerance (seconds)
webhook.verify(rawBody, headers, { tolerance: 600 });
// Check the signature only, without the timestamp check or JSON parsing.
// verifySignature takes the three header values as strings.
webhook.verifySignature(
rawBody,
String(headers['webhook-id']),
String(headers['webhook-timestamp']),
String(headers['webhook-signature'])
);Both methods throw HookbaseWebhookVerificationError when verification fails.
Test Headers
import { createWebhook } from '@hookbase/sdk';
const testWebhook = createWebhook('whsec_dGVzdF9zZWNyZXRfZm9yX2xvY2FsX2Rldg==');
const body = JSON.stringify({ orderId: 'test_123' });
const testHeaders = testWebhook.generateTestHeaders(body);
// Send body with testHeaders to your handlerUse a whsk_ Ed25519 secret key to generate v1a test headers. A whpk_ public key cannot sign.
Error Handling
import {
HookbaseApiError,
HookbaseRateLimitError,
HookbaseValidationError,
HookbaseNotFoundError,
HookbaseTimeoutError,
HookbaseNetworkError,
} from '@hookbase/sdk';
try {
await hookbase.messages.send(appId, message);
} catch (error) {
if (error instanceof HookbaseRateLimitError) {
console.log(`Rate limited, retry after ${error.retryAfter}s`);
} else if (error instanceof HookbaseValidationError) {
console.error('Invalid request:', error.message, error.validationErrors);
} else if (error instanceof HookbaseNotFoundError) {
console.error('Not found');
} else if (error instanceof HookbaseApiError) {
console.error('API error:', error.status, error.message, error.code, error.requestId);
} else if (error instanceof HookbaseTimeoutError || error instanceof HookbaseNetworkError) {
console.error('Could not reach Hookbase:', error.message);
}
}| Error | When |
|---|---|
HookbaseAuthenticationError | 401 |
HookbaseForbiddenError | 403 |
HookbaseNotFoundError | 404 |
HookbaseValidationError | 400 or 422. validationErrors holds field errors. |
HookbaseRateLimitError | 429. retryAfter is in seconds. |
HookbaseApiError | Any other error status. The classes above extend it. |
HookbaseTimeoutError | The request exceeded timeout. |
HookbaseNetworkError | The request failed before a response arrived. |
HookbaseWebhookVerificationError | Webhook verification failed. |
Every error extends HookbaseError.
Inbound Resources
The same client manages inbound webhooks: sources, destinations, routes, transforms, filters, schemas, events, deliveries and providers. It also has dlq, analytics, apiKeys, tunnels and cronJobs.
// Which secret does a provider need?
const stripeInfo = await hookbase.providers.get('stripe');
const source = await hookbase.sources.create({
name: 'Stripe',
slug: 'stripe',
provider: 'stripe',
signingSecret: 'whsec_...', // Issued by Stripe
});
// Inbound event lists also use cursors
const { data: events, cursor } = await hookbase.events.list({ sourceId: 'stripe', limit: 50 });apiKeys.create requires a dashboard user session. A client authenticated with a whr_ API key gets HookbaseForbiddenError.
TypeScript Support
The SDK is fully typed. Import types as needed:
import type {
Application,
Endpoint,
EventType,
Subscription,
OutboundMessage,
SendMessageInput,
SendMessageResponse,
RequestOptions,
} from '@hookbase/sdk';