Skip to content
Halyardv2.4.1
DocsAPIErrors
GitHub
Documentationv2.4.1
  • Getting started
    • Overview
    • Installation
    • Quickstart
  • Concepts
    • Delivery & retries
    • Signature verification
  • Reference
    • Endpoint reference
    • Error codes
    • Getting help
v2.4.1

Typed webhooks that retry themselves

Halyard delivers events to your endpoint with exponential backoff, signed payloads and a replay log you can actually query. One dependency, no queue to run.

QuickstartAPI reference
Overview

What Halyard does

You POST an event. We deliver it, retry it on failure with exponential backoff for up to 72 hours, sign every attempt, and keep a replayable log. If your endpoint was down for an afternoon, nothing is lost.

Delivery window
72 hours, 14 attempts, exponential backoff from 5s
Signing
HMAC-SHA256 over the raw body, with a rotating secret
Ordering
Per-destination FIFO; a stuck event does not block others
Replay
Any delivery, any time in the retention window, by id or filter
Installation

Install the SDK

One package, no peer dependencies. Node 20+, Deno and Bun are all supported by the same build.

npm install @halyard/sdk
Quickstart

Send your first event

Create a client, register a destination, publish. The destination is created once and reused — publishing to an unknown destination is an error rather than an implicit create, so a typo cannot silently open a new stream.

send-event.ts
import { Halyard } from '@halyard/sdk';

const halyard = new Halyard({ apiKey: process.env.HALYARD_KEY! });

// Destinations are created once, then referenced by id.
const destination = await halyard.destinations.create({
  url: 'https://api.example.com/webhooks/halyard',
  events: ['invoice.paid', 'invoice.voided'],
});

await halyard.publish({
  destination: destination.id,
  type: 'invoice.paid',
  data: { invoiceId: 'in_4f2a', amountMinor: 24_900, currency: 'NOK' },
});
zsh
$ node send-event.ts   halyard  destination created   dst_8Kq2Rm  halyard  published             evt_01HXQ… → dst_8Kq2Rm  halyard  delivered             204 in 118ms (attempt 1/14) Done in 0.94s
Concepts

Delivery and retries

A delivery is one attempt at one destination. Any 2xx is success; anything else is retried on the schedule below until the window closes.

AttemptDelayElapsed
1immediate0s
25s5s
330s35s
42m2m 35s
5–8×4 each~2h
9–14×2, capped at 6h72h
Concepts

Verify the signature

Every attempt carries a `Halyard-Signature` header. Verify it against the raw request body — not the parsed JSON, whose key order your framework is free to change.

app/api/webhooks/route.ts
import { verify } from '@halyard/sdk/webhook';

export async function POST(request: Request) {
  const raw = await request.text();          // raw body, not request.json()
  const signature = request.headers.get('Halyard-Signature');

  if (!verify(raw, signature, process.env.HALYARD_SIGNING_SECRET!)) {
    return new Response('bad signature', { status: 401 });
  }

  const event = JSON.parse(raw);
  // ... handle it, then return any 2xx within 10 seconds.
  return new Response(null, { status: 204 });
}
Reference

POST /v2/publish

Publishes one event to one destination. Idempotent on `idempotencyKey` for 24 hours.

ParameterTypeDefaultDescription
destinationrequired
string—

Destination id. Publishing to an unknown id is a 404, never an implicit create.

typerequired
string—

Event type. Must be one the destination subscribes to, or the call is a 422.

datarequired
object—

Your payload. Serialised verbatim; the signature covers exactly these bytes.

idempotencyKey
stringauto

Replaying the same key within 24h returns the original event instead of a duplicate.

notBefore
string (ISO 8601)now

Hold the event until this instant. Useful for scheduled reminders.

maxAttempts
number14

Lower the ceiling for events that stop being useful. Cannot be raised above 14.

Reference

Error codes

Every error carries a stable `code`. Match on that, never on the message — messages are written for humans and we improve them.

The destination id does not exist, or belongs to a different project. Destinations are project-scoped; a key from another project will produce this rather than a 403, deliberately — we do not confirm the existence of resources you cannot see.
The destination is not subscribed to this event type. Add it with `destinations.update({ events })`. This is a hard error rather than a silent drop, because a silently dropped webhook is the single most expensive bug in this category of product.
You called `secrets.rotate()` and the previous secret is still in its grace window. Both secrets verify during the window; wait for it to close, or pass `force: true` to end it immediately and invalidate the old one.
The serialised `data` exceeds 256 KiB. Send a reference rather than the object — a webhook that carries a whole document tends to become the transport for that document.
Reference

Getting help

Status, source and a real inbox. There is no chatbot.

status.halyard.devgithub.com/halyard/sdksupport@halyard.dev
On this page
  • Overview
  • Installation
  • Quickstart
  • Delivery & retries
  • Signature verification
  • Endpoint reference
  • Error codes
  • Getting help
DocsAPIErrorsSupport

Halyard v2.4.1 · MIT licensed

TemplatesHalyard — Developer docs
Variant↳ other