@webhook-platform/node
v2.2.1
Published
Official Node.js SDK for Hookflow — reliable webhook infrastructure
Maintainers
Readme
@webhook-platform/node
Official Node.js SDK for Hookflow.
Installation
npm install @webhook-platform/nodeQuick Start
import { Hookflow } from '@webhook-platform/node';
const client = new Hookflow({
apiKey: 'wh_live_your_api_key',
baseUrl: 'http://localhost:8080', // optional, defaults to localhost
});
// Send an event
const event = await client.events.send({
type: 'order.completed',
data: {
orderId: 'ord_12345',
amount: 99.99,
currency: 'USD',
},
});
console.log(`Event created: ${event.eventId}`);
console.log(`Deliveries created: ${event.deliveriesCreated}`);API Reference
Events
// Send event with idempotency key
const event = await client.events.send(
{ type: 'order.completed', data: { orderId: '123' } },
'unique-idempotency-key'
);Endpoints
// Create endpoint
const endpoint = await client.endpoints.create(projectId, {
url: 'https://api.example.com/webhooks',
description: 'Production webhooks',
enabled: true,
});
// List endpoints
const endpoints = await client.endpoints.list(projectId);
// Update endpoint
await client.endpoints.update(projectId, endpointId, {
enabled: false,
});
// Delete endpoint
await client.endpoints.delete(projectId, endpointId);
// Rotate secret
const updated = await client.endpoints.rotateSecret(projectId, endpointId);
console.log(`New secret: ${updated.secret}`);
// Test endpoint connectivity
const result = await client.endpoints.test(projectId, endpointId);
console.log(`Test ${result.success ? 'passed' : 'failed'}: ${result.latencyMs}ms`);Subscriptions
// Subscribe endpoint to an event type
const subscription = await client.subscriptions.create(projectId, {
endpointId: endpoint.id,
eventType: 'order.completed',
enabled: true,
});
// List subscriptions
const subscriptions = await client.subscriptions.list(projectId);
// Update subscription
await client.subscriptions.update(projectId, subscriptionId, {
eventType: 'order.shipped',
enabled: true,
});
// Delete subscription
await client.subscriptions.delete(projectId, subscriptionId);Deliveries
// List deliveries with filters
const deliveries = await client.deliveries.list(projectId, {
status: 'FAILED',
page: 0,
size: 20,
});
console.log(`Total failed: ${deliveries.totalElements}`);
// Get delivery attempts
const attempts = await client.deliveries.getAttempts(deliveryId);
for (const attempt of attempts) {
console.log(`Attempt ${attempt.attemptNumber}: ${attempt.httpStatus} (${attempt.latencyMs}ms)`);
}
// Replay failed delivery
await client.deliveries.replay(deliveryId);Incoming Webhooks
Receive, validate, and forward webhooks from third-party providers (Stripe, GitHub, Twilio, etc.).
Incoming Sources
// Create an incoming source with HMAC verification
const source = await client.incomingSources.create(projectId, {
name: 'Stripe Webhooks',
slug: 'stripe',
providerType: 'STRIPE',
verificationMode: 'HMAC_GENERIC',
hmacSecret: 'whsec_...',
hmacHeaderName: 'Stripe-Signature',
});
console.log(`Ingress URL: ${source.ingressUrl}`);
// List sources
const sources = await client.incomingSources.list(projectId);
// Update source
await client.incomingSources.update(projectId, sourceId, {
name: 'Stripe Production',
rateLimitPerSecond: 100,
});
// Delete source
await client.incomingSources.delete(projectId, sourceId);Incoming Destinations
// Add a forwarding destination
const dest = await client.incomingSources.createDestination(projectId, sourceId, {
url: 'https://your-api.com/webhooks/stripe',
enabled: true,
maxAttempts: 5,
timeoutSeconds: 30,
});
// List destinations
const dests = await client.incomingSources.listDestinations(projectId, sourceId);
// Update destination
await client.incomingSources.updateDestination(projectId, sourceId, destId, {
enabled: false,
});
// Delete destination
await client.incomingSources.deleteDestination(projectId, sourceId, destId);Incoming Events
// List incoming events (with optional source filter)
const events = await client.incomingEvents.list(projectId, {
sourceId: source.id,
page: 0,
size: 20,
});
// Get event details
const event = await client.incomingEvents.get(projectId, eventId);
// Get forward attempts for an event
const attempts = await client.incomingEvents.getAttempts(projectId, eventId);
// Replay event to all destinations
const result = await client.incomingEvents.replay(projectId, eventId);
console.log(`Replayed to ${result.destinationsCount} destinations`);Webhook Signature Verification
Verify incoming webhooks in your endpoint:
import { verifySignature, constructEvent } from '@webhook-platform/node';
app.post('/webhooks', (req, res) => {
const payload = req.body; // raw body string
const signature = req.headers['x-signature'];
const secret = process.env.WEBHOOK_SECRET;
try {
// Option 1: Just verify
verifySignature(payload, signature, secret);
// Option 2: Verify and parse
const event = constructEvent(payload, req.headers, secret);
console.log(`Received ${event.type}:`, event.data);
// Handle the event
switch (event.type) {
case 'order.completed':
handleOrderCompleted(event.data);
break;
}
res.status(200).send('OK');
} catch (err) {
console.error('Webhook verification failed:', err.message);
res.status(400).send('Invalid signature');
}
});Express.js Example
import express from 'express';
import { constructEvent } from '@webhook-platform/node';
const app = express();
// Important: Use raw body for signature verification
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const event = constructEvent(
req.body.toString(),
req.headers,
process.env.WEBHOOK_SECRET
);
// Process event...
res.sendStatus(200);
});Error Handling
import {
HookflowError,
RateLimitError,
AuthenticationError,
ValidationError
} from '@webhook-platform/node';
try {
await client.events.send({ type: 'test', data: {} });
} catch (err) {
if (err instanceof RateLimitError) {
// Wait and retry
console.log(`Rate limited. Retry after ${err.retryAfter}ms`);
await sleep(err.retryAfter);
} else if (err instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (err instanceof ValidationError) {
console.error('Validation failed:', err.fieldErrors);
} else if (err instanceof HookflowError) {
console.error(`Error ${err.status}: ${err.message}`);
}
}Error Response Format
All API errors return a consistent JSON body:
{
"error": "error_code",
"message": "Human-readable description",
"status": 400,
"fieldErrors": { "field": "reason" }
}error— machine-readable error code (snake_case), always presentmessage— human-readable description, always presentstatus— HTTP status code (integer), always presentfieldErrors— field-level validation details (only present forvalidation_error)
Error Codes Reference
| HTTP Status | error Code | SDK Exception | Description |
|---|---|---|---|
| 400 | validation_error | ValidationError | Invalid request parameters; see fieldErrors |
| 400 | invalid_request | HookflowError | Malformed or semantically invalid request |
| 401 | unauthorized | AuthenticationError | Missing or invalid API key / expired token |
| 403 | forbidden | HookflowError | Insufficient permissions for the action |
| 404 | not_found | NotFoundError | Requested resource does not exist |
| 413 | payload_too_large | HookflowError | Request body exceeds maximum allowed size |
| 422 | unprocessable_entity | HookflowError | Valid syntax but violates business rules |
| 429 | rate_limit_exceeded | RateLimitError | Too many requests; check X-RateLimit-* headers |
| 500 | internal_error | HookflowError | Unexpected server error |
Generic Requests
As the API expands, you can call any endpoint directly without waiting for SDK updates:
// GET
const schemas = await client.get<any[]>('/api/v1/projects/proj_123/schemas');
// POST with body and idempotency key
const result = await client.post('/api/v1/some/new/endpoint', { key: 'value' }, 'idempotency-key');
// PUT
await client.put('/api/v1/projects/proj_123/settings', { timezone: 'UTC' });
// PATCH
await client.patch('/api/v1/projects/proj_123/settings', { timezone: 'UTC' });
// DELETE
await client.delete('/api/v1/projects/proj_123/tags/old-tag');
// Fully custom request (any HTTP method)
const data = await client.request<any>('OPTIONS', '/api/v1/some/path');All generic methods use the same authentication, error handling, and rate-limit logic as the built-in methods.
Configuration
const client = new Hookflow({
apiKey: 'wh_live_xxx', // Required: Your API key
baseUrl: 'https://api.example.com', // Optional: API base URL
timeout: 30000, // Optional: Request timeout in ms (default: 30000)
});TypeScript Support
This SDK is written in TypeScript and includes full type definitions:
import type {
Event,
EventResponse,
Endpoint,
Delivery,
DeliveryStatus
} from '@webhook-platform/node';Development
Running Tests
Local (requires Node.js 16+):
npm install
npm testDocker:
docker run --rm -v $(pwd):/app -w /app node:20-alpine sh -c "npm install && npm test"Building
npm run buildLicense
MIT
