Skip to main content

Relae Node SDK

npm version License: MIT

Official Node.js SDK for Relae - A reliable webhook delivery platform with automatic retries, observability, and dead letter queue management.

Features

  • 🚀 Simple API - Clean, intuitive interface for webhook operations
  • 🔄 Dead Letter Queue - Manage and replay failed webhook deliveries
  • 📊 Delivery Tracking - Monitor webhook delivery attempts and status
  • 🔒 Type Safe - Full TypeScript support with complete type definitions
  • Webhook Verification - Built-in signature verification utilities
  • Pagination Support - Async generators and helper methods for handling large datasets
  • 🛡️ Error Handling - Specific error classes for different failure scenarios

Installation

npm install @relae/node

or with yarn:

yarn add @relae/node

Quick Start

import { Relae } from "relae";

// Initialize the client with your API key
const relae = new Relae("your-api-key");

// List all destinations
const { destinations } = await relae.destinations.list();
console.log(destinations);

// Create a new destination
await relae.destinations.create({
source: "stripe",
destination_url: "https://myapp.com/webhooks/stripe",
headers: [{ key: "X-Custom-Header", value: "value" }],
});

// List webhook events with pagination
const webhooksPage = await relae.webhooks.list();
console.log(webhooksPage.events);

// Or iterate through all events using async generator
for await (const event of relae.webhooks.all()) {
console.log(event);
}

API Reference

Client Initialization

import { Relae } from "relae";

const relae = new Relae(apiKey: string);

The client provides access to four main resources:

  • destinations - Manage webhook destinations
  • webhooks - View webhook delivery events
  • deadLetter - Manage failed webhook events
  • utils - Utility methods for webhook verification

Destinations API

Manage webhook destinations where your events are delivered.

destinations.list()

List all webhook destinations in your account.

const response = await relae.destinations.list();

// Returns:
interface DestinationListResponse {
destinations: Destination[];
}

interface Destination {
id: string;
account_id: string;
source: string;
destination_url: string;
headers: { key: string; value: string }[];
vendor_webhook_secret?: string;
endpoint_token: string;
webhook_url: string;
is_active: boolean;
last_used_at?: string | null;
created_at: string;
updated_at: string;
}

Example:

const { destinations } = await relae.destinations.list();
destinations.forEach((dest) => {
console.log(`${dest.source}: ${dest.destination_url}`);
});

destinations.create(input)

Create a new webhook destination.

await relae.destinations.create({
source: string, // e.g., "stripe", "github"
destination_url: string, // Your webhook endpoint URL
headers?: Record<string, string>[], // Optional custom headers
vendor_webhook_secret?: string, // Optional vendor secret
});

Example:

await relae.destinations.create({
source: "stripe",
destination_url: "https://api.myapp.com/webhooks/stripe",
headers: [{ key: "X-Custom-Auth", value: "secret-token" }],
vendor_webhook_secret: "whsec_...",
});

destinations.update(id, input)

Update an existing destination.

await relae.destinations.update(
id: string,
input: Partial<DestinationInput>
);

Example:

await relae.destinations.update("dest_123", {
destination_url: "https://api.myapp.com/webhooks/stripe-v2",
headers: [{ key: "X-API-Version", value: "2024-01" }],
});

destinations.delete(id)

Delete a destination.

await relae.destinations.delete(id: string);

Example:

await relae.destinations.delete("dest_123");

Webhooks API

View and monitor webhook delivery events.

webhooks.list(cursor?)

List webhook events with pagination support.

const page = await relae.webhooks.list(cursor?: string);

// Returns:
interface PaginatedWebhookEvents {
events: WebhookEvent[];
next_cursor?: string;
}

interface WebhookEvent {
id: string;
account_id: string;
source: string;
payload: Record<string, any>;
headers: Record<string, any>;
status: string;
retry_count: number;
created_at: string;
updated_at: string;
duration: number; // Delivery duration in ms
}

Example:

// Get first page
const firstPage = await relae.webhooks.list();
console.log(firstPage.events);

// Get next page if available
if (firstPage.next_cursor) {
const nextPage = await relae.webhooks.list(firstPage.next_cursor);
console.log(nextPage.events);
}

webhooks.all()

Async generator to iterate through all webhook events across all pages.

for await (const event of relae.webhooks.all()) {
console.log(`Event ${event.id}: ${event.status}`);
console.log(`Duration: ${event.duration}ms, Retries: ${event.retry_count}`);
}

webhooks.listAllPages()

Fetch all pages at once and return as an array of pages.

const allPages: WebhookEvent[][] = await relae.webhooks.listAllPages();

// Flatten if needed
const allEvents = allPages.flat();
console.log(`Total events: ${allEvents.length}`);

Dead Letter Queue API

Manage and replay failed webhook deliveries.

deadLetter.list(cursor?)

List failed webhook events in the dead letter queue.

const page = await relae.deadLetter.list(cursor?: string);

// Returns:
interface PaginatedDeadLetterEvents {
events: DeadLetterEvent[];
next_cursor?: string;
}

interface DeadLetterEvent {
id: string;
orig_id: string; // Original webhook event ID
account_id: string;
source: string;
payload: Record<string, any>;
headers: Record<string, any>;
retry_count: number;
failed_reason: string; // Reason for failure
created_at: string;
}

Example:

const { events } = await relae.deadLetter.list();
events.forEach((event) => {
console.log(`Failed: ${event.source} - ${event.failed_reason}`);
});

deadLetter.replay(eventId)

Retry a single failed webhook event.

await relae.deadLetter.replay(eventId: string);

Example:

// Replay a specific failed event
await relae.deadLetter.replay("dlq_abc123");

deadLetter.replayMulti(input)

Retry multiple failed webhook events at once.

await relae.deadLetter.replayMulti({
events: string[] // Array of event IDs
});

Example:

// Replay multiple failed events
await relae.deadLetter.replayMulti({
events: ["dlq_abc123", "dlq_def456", "dlq_ghi789"],
});

deadLetter.all()

Async generator to iterate through all dead letter events.

for await (const event of relae.deadLetter.all()) {
console.log(`Failed event: ${event.id}`);
console.log(`Reason: ${event.failed_reason}`);

// Optionally replay
if (event.retry_count < 3) {
await relae.deadLetter.replay(event.id);
}
}

deadLetter.listAllPages()

Fetch all dead letter queue pages at once.

const allPages: DeadLetterEvent[][] = await relae.deadLetter.listAllPages();
const allFailedEvents = allPages.flat();

console.log(`Total failed events: ${allFailedEvents.length}`);

Utils API

Utility methods for webhook handling.

Utils.verifyRelaeSignature(body, signature, secret)

Verify the authenticity of an incoming Relae webhook using HMAC signature verification.

import { Utils } from "relae";

const isValid = Utils.verifyRelaeSignature(
body: string, // Raw request body as string
signature: string, // X-Relae-Signature header value
secret: string // Your webhook secret
toleranceSec?: number // Optional: max age in seconds (default 300)
): boolean;

Example with Express:

import express from "express";
import { Utils } from "relae";

const app = express();

app.post(
"/webhooks/relae",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-relae-signature"] as string;
const secret = process.env.RELAE_WEBHOOK_SECRET!;
const body = req.body.toString();

if (!Utils.verifyRelaeSignature(body, signature, secret)) {
return res.status(401).send("Invalid signature");
}

const payload = JSON.parse(body);
console.log("Valid webhook received:", payload);

res.status(200).send("OK");
},
);

Signature Format:

The X-Relae-Signature header has the format: t=<timestamp>,v1=<signature>


Error Handling

The SDK provides specific error classes for different failure scenarios:

import {
RelaeError, // Base error class
UnauthorizedError, // 401 - Invalid API key
ForbiddenError, // 403 - Insufficient permissions
NotFoundError, // 404 - Resource not found
RateLimitError, // 429 - Rate limit exceeded
InternalServerError, // 500 - Server error
} from "relae";

Example:

try {
await relae.destinations.create({
source: "stripe",
destination_url: "https://api.myapp.com/webhooks",
});
} catch (error) {
if (error instanceof UnauthorizedError) {
console.error("Invalid API key");
} else if (error instanceof RateLimitError) {
console.error("Rate limit exceeded, please retry later");
} else if (error instanceof RelaeError) {
console.error(`API error: ${error.message} (${error.status})`);
console.error("Raw response:", error.raw);
} else {
console.error("Unexpected error:", error);
}
}

All RelaeError instances include:

  • message - Error description
  • status - HTTP status code (if applicable)
  • raw - Raw error response from the API

TypeScript Support

The SDK is fully written in TypeScript with complete type definitions.

import {
Relae,
Destination,
DestinationInput,
WebhookEvent,
DeadLetterEvent,
PaginatedWebhookEvents,
PaginatedDeadLetterEvents,
} from "relae";

const relae = new Relae("your-api-key");

// All responses are fully typed
const response: PaginatedWebhookEvents = await relae.webhooks.list();
const events: WebhookEvent[] = response.events;

Advanced Examples

Monitoring Failed Webhooks

// Check dead letter queue and replay recent failures
const { events } = await relae.deadLetter.list();

const recentFailures = events.filter((event) => {
const hourAgo = Date.now() - 60 * 60 * 1000;
return new Date(event.created_at).getTime() > hourAgo;
});

if (recentFailures.length > 0) {
console.log(`Found ${recentFailures.length} recent failures`);

await relae.deadLetter.replayMulti({
events: recentFailures.map((e) => e.id),
});

console.log("Replayed all recent failures");
}

Processing All Events in Batches

// Process all webhook events in batches of 100
const batchSize = 100;
let batch: WebhookEvent[] = [];

for await (const event of relae.webhooks.all()) {
batch.push(event);

if (batch.length >= batchSize) {
await processBatch(batch);
batch = [];
}
}

// Process remaining events
if (batch.length > 0) {
await processBatch(batch);
}

async function processBatch(events: WebhookEvent[]) {
console.log(`Processing ${events.length} events...`);
// Your batch processing logic
}

Creating Multiple Destinations

const sources = [
{ name: "stripe", url: "https://api.myapp.com/webhooks/stripe" },
{ name: "github", url: "https://api.myapp.com/webhooks/github" },
{ name: "slack", url: "https://api.myapp.com/webhooks/slack" },
];

for (const source of sources) {
await relae.destinations.create({
source: source.name,
destination_url: source.url,
});
console.log(`Created destination for ${source.name}`);
}

Authentication

All API requests use Bearer token authentication:

Authorization: Bearer <your-api-key>

Get your API key from your Relae account page.

⚠️ Security: Never commit API keys to version control. Use environment variables:

const relae = new Relae(process.env.RELAE_API_KEY!);

Request IDs

Every request automatically includes a unique X-Request-Id header for tracking and debugging purposes. This can be useful when contacting support about specific API calls.


License

MIT License - see LICENSE file for details.


Support


Contributing

Contributions are welcome! Please feel free to submit a Pull Request.