Skip to content

WillBallentine/relae-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

Relae SDK for Node

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors