Skip to content

ysrdevs/better-auth-webhooks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

better-auth-webhooks

Webhook plugin for Better Auth — fire HTTP webhooks on auth events with HMAC-SHA256 signing and automatic retries.

Features

  • 9 auth events — user, session, and account create/update/delete
  • HMAC-SHA256 signing — every payload is signed for verification
  • Automatic retries — exponential backoff with configurable retry count
  • Sensitive field stripping — passwords, tokens, and secrets are never sent
  • Fire and forget — webhooks are delivered asynchronously, never blocking auth flows
  • Dynamic endpoints — configure statically or resolve from a database at runtime
  • Verify helper — timing-safe signature verification with timestamp tolerance

Installation

npm install @pinklemon8/better-auth-webhooks

Quick Start

1. Add the Plugin

import { betterAuth } from "better-auth";
import { webhooks } from "@pinklemon8/better-auth-webhooks";

export const auth = betterAuth({
  // ...your config
  plugins: [
    webhooks({
      endpoints: [
        {
          url: "https://your-app.com/api/webhooks/auth",
          secret: process.env.WEBHOOK_SECRET!,
          // Optional: only receive specific events
          events: ["user.created", "user.deleted", "session.created"],
        },
        {
          url: "https://your-crm.com/webhooks",
          secret: process.env.CRM_WEBHOOK_SECRET!,
          // Omit events to receive all
        },
      ],
      // Optional configuration
      retry: {
        maxRetries: 3,         // default: 3
        initialDelayMs: 1000,  // default: 1000
        backoffMultiplier: 2,  // default: 2
      },
      timeoutMs: 10000,        // default: 10000
      onError: (err) => console.error(`Webhook failed: ${err.endpoint} - ${err.error}`),
      onSuccess: (res) => console.log(`Webhook delivered: ${res.endpoint} - ${res.event}`),
    }),
  ],
});

2. Verify Incoming Webhooks

On the receiving end, verify the signature:

import { verifyWebhook } from "@pinklemon8/better-auth-webhooks/verify";

// Express / Node.js
app.post("/api/webhooks/auth", (req, res) => {
  try {
    const payload = verifyWebhook({
      body: req.body, // raw string body
      signature: req.headers["x-webhook-signature"],
      secret: process.env.WEBHOOK_SECRET!,
      tolerance: 300, // reject if older than 5 minutes (default)
    });

    switch (payload.event) {
      case "user.created":
        // Provision resources, send welcome email, etc.
        break;
      case "session.created":
        // Track login analytics
        break;
      case "user.deleted":
        // Cleanup external resources
        break;
    }

    res.status(200).json({ received: true });
  } catch (err) {
    res.status(401).json({ error: err.message });
  }
});

Next.js App Router:

import { verifyWebhookRequest } from "@pinklemon8/better-auth-webhooks/verify";

export async function POST(request: Request) {
  const body = await request.text();
  try {
    const payload = verifyWebhookRequest(
      { body, headers: request.headers },
      process.env.WEBHOOK_SECRET!
    );

    // Handle payload.event...
    return Response.json({ received: true });
  } catch {
    return Response.json({ error: "Invalid signature" }, { status: 401 });
  }
}

Events

Event Trigger
user.created New user registered
user.updated User profile updated
user.deleted User account deleted
session.created User signed in
session.updated Session refreshed or modified
session.deleted User signed out or session revoked
account.created OAuth/wallet account linked
account.updated Linked account updated
account.deleted Linked account removed

Webhook Payload

Every webhook POST request includes:

Headers:

Header Description
X-Webhook-Id Unique delivery ID (e.g. whk_a1b2c3...)
X-Webhook-Event Event type (e.g. user.created)
X-Webhook-Timestamp ISO 8601 timestamp
X-Webhook-Signature sha256=<hex> HMAC signature

Body:

{
  "id": "whk_a1b2c3d4e5f6...",
  "event": "user.created",
  "timestamp": "2026-04-07T12:00:00.000Z",
  "data": {
    "id": "user_123",
    "name": "John Doe",
    "email": "john@example.com",
    "emailVerified": true,
    "createdAt": "2026-04-07T12:00:00.000Z"
  }
}

Sensitive fields (passwords, tokens, secrets) are automatically stripped from the data object.

Dynamic Endpoints

Load webhook endpoints from a database:

webhooks({
  endpoints: async () => {
    const rows = await db.select().from(webhookEndpoints);
    return rows.map((row) => ({
      url: row.url,
      secret: row.secret,
      events: row.events as WebhookEvent[],
    }));
  },
});

Retry Behavior

  • 4xx errors (except 429): Not retried (client error)
  • 5xx errors, timeouts, network failures: Retried with exponential backoff
  • Default: 3 retries with delays of 1s, 2s, 4s

Security

  • Payloads are signed with HMAC-SHA256 using a per-endpoint secret
  • Verification uses timingSafeEqual to prevent timing attacks
  • Timestamp tolerance rejects replayed webhooks (default: 5 minutes)
  • Sensitive fields (password, accessToken, refreshToken, etc.) are stripped before delivery

API Reference

webhooks(options)

Option Type Default Description
endpoints WebhookEndpoint[] | () => Promise<WebhookEndpoint[]> required Webhook destinations
retry.maxRetries number 3 Max delivery attempts
retry.initialDelayMs number 1000 First retry delay
retry.backoffMultiplier number 2 Backoff multiplier
timeoutMs number 10000 Request timeout
onError (err) => void Error callback
onSuccess (res) => void Success callback

verifyWebhook(options)

Option Type Default Description
body string required Raw request body
signature string required X-Webhook-Signature header value
secret string required Webhook secret
tolerance number 300 Max age in seconds

verifyWebhookRequest(request, secret, tolerance?)

Convenience wrapper that extracts the signature header automatically.

License

MIT

About

Webhook plugin for Better Auth. Fire HTTP webhooks on auth events with HMAC-SHA256 signing and automatic retries.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors