npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@better-webhook/resend

v0.1.1

Published

Resend module for better-webhook

Downloads

73

Readme

@better-webhook/resend

npm

Type-safe Resend webhook handling for better-webhook.

Features

  • Typed webhook envelopes for all 17 documented Resend event types
  • Automatic Svix-compatible signature verification using svix-* headers
  • Default signed timestamp freshness checks (300 seconds)
  • Replay key support via svix-id
  • Tree-shakeable event exports from @better-webhook/resend/events
  • Verified but unhandled events acknowledge with 200 to match Resend's delivery contract

Installation

npm install @better-webhook/resend @better-webhook/core
# or
pnpm add @better-webhook/resend @better-webhook/core
# or
yarn add @better-webhook/resend @better-webhook/core

Install one adapter package too:

# Pick one:
npm install @better-webhook/nextjs
npm install @better-webhook/express
npm install @better-webhook/nestjs
npm install @better-webhook/hono
npm install @better-webhook/gcp-functions

Quick Start

import { resend } from "@better-webhook/resend";
import {
  email_bounced,
  email_delivered,
  email_received,
} from "@better-webhook/resend/events";
import { toNextJS } from "@better-webhook/nextjs";

const webhook = resend({
  secret: process.env.RESEND_WEBHOOK_SECRET,
})
  .event(email_delivered, async (payload) => {
    console.log("delivered:", payload.data.email_id);
  })
  .event(email_bounced, async (payload) => {
    console.log("bounce type:", payload.data.bounce.type);
  })
  .event(email_received, async (payload) => {
    console.log("inbound message:", payload.data.message_id);
  });

export const POST = toNextJS(webhook);

Supported Events

Email Events

  • email_sent (email.sent)
  • email_scheduled (email.scheduled)
  • email_delivered (email.delivered)
  • email_delivery_delayed (email.delivery_delayed)
  • email_complained (email.complained)
  • email_bounced (email.bounced)
  • email_opened (email.opened)
  • email_clicked (email.clicked)
  • email_received (email.received)
  • email_failed (email.failed)
  • email_suppressed (email.suppressed)

Domain Events

  • domain_created (domain.created)
  • domain_updated (domain.updated)
  • domain_deleted (domain.deleted)

Contact Events

  • contact_created (contact.created)
  • contact_updated (contact.updated)
  • contact_deleted (contact.deleted)

Signature Verification

Resend signs webhook requests with Svix-compatible headers:

  • svix-id
  • svix-timestamp
  • svix-signature

This package verifies the exact raw body using HMAC-SHA256 over:

${svixId}.${svixTimestamp}.${rawBody}

using the base64-decoded portion of the whsec_... signing secret.

Verification behavior:

  • rejects missing svix-* headers
  • rejects malformed timestamps
  • rejects stale or far-future timestamps outside the configured tolerance window
  • accepts any valid v1 entry in a multi-signature svix-signature header

Use the raw request body exactly as Resend sent it. Re-serializing JSON before verification will break the signature.

Replay Protection and Idempotency

Resend delivers webhooks with at-least-once semantics and may retry or replay the same message. The provider exposes:

  • context.deliveryId from svix-id
  • replay metadata via svix-id + svix-timestamp

With core replay protection enabled, duplicate svix-id values return 409 by default:

import { createInMemoryReplayStore } from "@better-webhook/core";

const webhook = resend({ secret: process.env.RESEND_WEBHOOK_SECRET })
  .withReplayProtection({
    store: createInMemoryReplayStore(),
  })
  .event(email_delivered, async (payload) => {
    await persistEvent(payload);
  });

For production, use a shared durable replay store so deduplication works across instances and restarts.

Payload Notes

  • Handlers receive the full Resend webhook envelope: { type, created_at, data }.
  • email.received webhooks contain metadata only. Fetch the full inbound body, headers, and attachments through Resend's receiving APIs if you need message content.
  • email.received payloads may omit data.subject; the schema normalizes a missing subject to "".
  • data.tags follows Resend's documented Record<string, string> shape.

Environment Variables

When no explicit secret is provided, Better Webhook resolves:

  1. RESEND_WEBHOOK_SECRET
  2. WEBHOOK_SECRET

License

MIT