Skip to main content
Introducing packages.sweber.dev
All posts
Release1 min read

Vector 0.6.0: send many events at once, skip repeated deliveries

sendMany sends up to 1000 events in one call, and new dedupers on the receiving side remember handled webhook ids in memory, Postgres, SQLite or MySQL.

By Seya Weber

Vector 0.6.0 is out. @sweberdev/vector 0.6.0 is on npm under the MIT licence and a drop-in update from 0.5. It adds one helper for the sending side and one for the receiving side.

Sending many events

After an import or a nightly job, a loop of send calls reads the tenant's endpoints again for every single event. sendMany does it once per tenant:

const results = await vector.sendMany(
  invoices.map((invoice) => ({
    eventType: "invoice.created",
    payload: invoice,
    tenant: invoice.accountId,
    idempotencyKey: `invoice-created-${invoice.id}`,
  })),
)

It takes up to 1000 events per call and checks all of them before it stores anything, so one bad payload does not leave half a batch behind. Results come back in the order of the inputs, and a repeated idempotency key inside the batch is recognised as a duplicate. It is not one database transaction: if the database fails midway, the first events are already stored, which is why every event should carry an idempotencyKey and the call can simply be repeated.

Skipping repeated deliveries

Webhooks are delivered at least once. If your answer is slow or gets lost, the same webhook-id arrives again. Receivers should skip ids they have handled, and that code is easy to get subtly wrong. Vector now ships it:

import { memoryDeduper, once, verifyRequest } from "@sweberdev/vector"

const deduper = memoryDeduper()

export async function POST(request: Request) {
  const { id, payload } = await verifyRequest(request, process.env.WEBHOOK_SECRET!)
  await once(deduper, id, () => handle(payload))
  return new Response(null, { status: 204 })
}

once runs the work only the first time. If the work throws, the id is forgotten again so the retry is handled, and the error is rethrown. With several processes or restarts, postgresDeduper, sqliteDeduper and mysqlDeduper keep the ids in a table instead. Ids are remembered for seven days, longer than the retry schedule, and prune() deletes old ones.

Update

pnpm add @sweberdev/vector@latest

The documentation covers both in the sending and receiving guides.

Package in this post

Vector

Self-hosted webhooks for Node.js and TypeScript: Standard Webhooks signatures, retries, a delivery log and SSRF protection, in your own PostgreSQL, MySQL or SQLite.

pnpm add @sweberdev/vector
View package

Related posts

Release

Vector 0.1.0 released

Self-hosted webhooks for Node.js and TypeScript: Standard Webhooks signatures, retries for 27 hours, a delivery log and SSRF protection, in your own PostgreSQL. A Svix alternative as a library.

Vector
Release

Vector 0.2.0: Ed25519 signatures, SQLite and MySQL

Vector can now sign webhooks with Ed25519, so your customers only hold a public key that cannot forge requests, and it keeps its data in SQLite or MySQL as well as PostgreSQL.

Vector