x-hook-service

1.0.1 • Public • Published

X-Hook Service

A concurrency-safe "track and retry" service for Webhooks (and other things?).

You provide the event storage and webhook request mechanism.
We provide the logic and sane defaults to retry failed webhooks with exponential backoff.
(and a bit of random jitter to prevent duplicate events on multiple servers)

Lightweight, zero-dependency.

Install

npm install --save x-hook-service@1

Usage

To run the service which retries failed webhooks in the background:

let XHookService = require("x-hook-service");

let xhookService = XHookService.create({
  store: store,
  retryWindow: 1 * 60 * 1000,
  retryOffset: 15 * 1000,
  // backoffMap: XHooks._backoffMap,
  random: Math.random(),
});

// fetches webhook events every minute
xhookService.run();

// for graceful shutdowns
// (not always necessary since weak timeout references are used)
await xhookService.stop();

Run a webhook immediately (typically the first time):

// schedules a webhook immediately
let date = new Date();
let event = { ulid: "01H9Y080000000000000000000", retries: 0, retry_at: null };
await xhookService.immediate(date, event);

Store & Event Implementation

You must provide a Store which can retrieve and update Webhook Events and Attempts (however you choose to track those).

They will be called by the service like this (with all the logic omitted):

let events = await store.anyIncomplete(nearFutureDate);
let event = await store.oneEvent(eventId);
eventId = store.getEventId(event);

let attempt = await store.addAttempt(attemptDate, { retry_at }, event);
let result = await store.runAttempt(attemptDate, attempt, event);
await store.updateAttempt(attemptDate, attempt, event, result);

The Store Interface

let store = {};

These are used to get a list of webhooks that should be attempted again, as well as the most up-to-date version of a specific event (it is checked just before a retry).

store.anyIncomplete = async function (nearFutureDate) {
  // return events for which the webhook should be tried again

  // Example:
  //     SELECT
  //         *
  //     FROM
  //         events
  //     WHERE
  //         completed_at is NULL
  //         AND
  //         retry_at <= :near_future_date

  return [event];
};

store.oneEvent = async function (eventId) {
  // return the event by the given id
  return event;
};

store.getEventId = function (event) {
  return event.ulid;
};

The actual running and reporting of the webhook is broken into 3 steps to ensure data consistency:

store.addAttempt = async function (attemptDate, { retry_at }, event) {
  // create and return the data representing a new webhook attempt

  // Example:
  //
  //     UPDATE
  //         events
  //     SET
  //         retries = :retries,
  //         retry_at = :retry_at
  //     WHERE
  //         event.ulid = :ulid

  return attempt;
};

store.runAttempt = async function (attemptDate, attempt, event) {
  // make the webhook request and return the result

  // Example:
  //
  //     let payload = JSON.stringify(event.details)
  //     let xHubSig = xHubSign(payload);
  //     fetch(event.webhook_url, {
  //         headers: { x-hub-signature-256: xHubSig },
  //         body: payload
  //     })

  return result;
};

store.updateAttempt = async function (attemptDate, attempt, event, result) {
  // update the record of attempt with the result

  // Example:
  //
  //     UPDATE
  //         events
  //     SET
  //         completed_at = CURRENT_TIMESTAMP,
  //         retry_at = null
  //     WHERE
  //         event.ulid = :ulid

  return;
};

The Event Interface

The event must have

  • some form of id (retrieved via getEventId())
  • a retries property indicating how many times the webhook has failed
  • retry_at, indicate the next time a webhook request should be tried
  • whatever details you need to attempt the webhook request
{
  ulid: '',
  retries: 0,
  retry_at: new Date(),
  // the rest is up to you
}

Package Sidebar

Install

npm i x-hook-service

Weekly Downloads

18

Version

1.0.1

License

SEE LICENSE IN LICENSE

Unpacked Size

32 kB

Total Files

7

Last publish

Collaborators

  • coolaj86