Skip to main content
The Resend Webhook Ingester is an open-source Next.js application that receives, verifies, and stores all your webhook events in your own database. Deploy it to your infrastructure and gain full control over your email event data.
For more details on why you should store your webhook data, see the data storage guide.

Why use the Webhook Ingester?

While you can build your own webhook handler, the Webhook Ingester provides a production-ready solution with:
  • Signature verification using Svix to ensure webhook authenticity
  • Idempotent storage that safely handles duplicate webhook deliveries
  • Multiple database support including PostgreSQL, MySQL, MongoDB, and data warehouses
  • One-click deployment to Vercel, Railway, or Render

Deploy

Get started in minutes with one-click deployment: Deploy with Vercel Deploy on Railway Deploy to Render Or use Docker:
docker pull ghcr.io/resend/resend-webhooks-ingester

Supported Databases

DatabaseEndpointBest For
Supabase/supabaseQuick setup with managed Postgres
PostgreSQL/postgresqlSelf-hosted or managed Postgres (Neon, Railway, Render)
MySQL/mysqlSelf-hosted or managed MySQL
PlanetScale/planetscaleServerless MySQL
MongoDB/mongodbDocument database (Atlas, self-hosted)
Snowflake/snowflakeData warehousing and analytics
BigQuery/bigqueryGoogle Cloud analytics
ClickHouse/clickhouseHigh-performance analytics

Quick Start

1

Clone and install

git clone https://github.com/resend/resend-webhooks-ingester.git
cd resend-webhooks-ingester
pnpm install
2

Configure environment variables

Copy the example environment file and add your credentials:
cp .env.example .env.local
At minimum, you need:
.env.local
# Required: Your Resend webhook signing secret
RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxx

# Database credentials (example for PostgreSQL)
POSTGRESQL_URL=postgresql://user:password@host:5432/database
Get your webhook signing secret from the Resend Dashboard when creating a webhook.
3

Set up your database

Set up your database and run the provided schema for your database. The ingester supports PostgreSQL, MySQL, MongoDB, and several data warehouses. Schema files can be found in the schemas/ directory.
pnpm db:setup --postgresql
# or use a different flag for a different database
4

Deploy and register webhook

Deploy to your preferred platform, then register your webhook endpoint in the Resend Dashboard and select all the events you’d like to store.Your endpoint URL will be: https://your-domain.com/{connector}For example: https://your-app.vercel.app/postgresql

Database Schemas

The ingester creates three tables to store webhook events:
TableDescription
resend_wh_emailsAll email events (sent, delivered, bounced, opened, clicked, etc.)
resend_wh_contactsContact events (created, updated, deleted)
resend_wh_domainsDomain events (created, updated, deleted)
Each table includes:
  • svix_id - Unique webhook event ID for idempotency
  • event_type - The type of event (e.g., email.delivered)
  • event_created_at - When the event occurred
  • webhook_received_at - When the webhook was received
  • Event-specific fields (email details, bounce info, click data, etc.)

Idempotency

The ingester handles duplicate webhooks automatically. Each webhook includes a unique svix-id header, and the ingester uses this to ensure events are stored only once. If Resend retries a webhook delivery (due to a temporary failure), the duplicate will be safely ignored without creating duplicate records in your database.

Configuration Reference

Required Environment Variables

VariableDescription
RESEND_WEBHOOK_SECRETYour webhook signing secret from Resend

Database-Specific Variables

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
POSTGRESQL_URL=postgresql://user:password@host:5432/database
MYSQL_URL=mysql://user:password@host:3306/database
PLANETSCALE_URL=mysql://username:password@host/database?ssl={"rejectUnauthorized":true}
MONGODB_URI=mongodb+srv://username:[email protected]/
MONGODB_DATABASE=resend_webhooks
SNOWFLAKE_ACCOUNT=your-account-identifier
SNOWFLAKE_USERNAME=your-username
SNOWFLAKE_PASSWORD=your-password
SNOWFLAKE_DATABASE=your-database
SNOWFLAKE_SCHEMA=your-schema
SNOWFLAKE_WAREHOUSE=your-warehouse
BIGQUERY_PROJECT_ID=your-project-id
BIGQUERY_DATASET_ID=your-dataset-id
BIGQUERY_CREDENTIALS={"type":"service_account","project_id":"..."}
CLICKHOUSE_URL=https://your-instance.clickhouse.cloud:8443
CLICKHOUSE_USERNAME=default
CLICKHOUSE_PASSWORD=your-password
CLICKHOUSE_DATABASE=default

Example Queries

Once your data is stored, you can run analytics queries. Here’s an example to get email status counts by day:
SELECT
  DATE(event_created_at) AS day,
  event_type,
  COUNT(*) AS count
FROM resend_wh_emails
GROUP BY DATE(event_created_at), event_type
ORDER BY day DESC, event_type;
See the queries_examples.md file in the repository for more analytics queries including bounce rates, open rates, and click-through rates.

Data Retention

By default, webhook events are stored indefinitely. To implement data retention policies, you can set up scheduled jobs to delete old events. Example for PostgreSQL (delete events older than 90 days):
DELETE FROM resend_wh_emails
WHERE event_created_at < NOW() - INTERVAL '90 days';
For Supabase, use pg_cron to schedule cleanup queries. For MongoDB, consider using TTL indexes or Atlas scheduled triggers.

Troubleshooting

  • Ensure RESEND_WEBHOOK_SECRET matches the signing secret in your Resend Dashboard - Make sure you’re using the raw request body for verification - Check that the secret hasn’t been rotated in Resend
  • Verify your database credentials are correct - Check that the schema has been applied to your database - Ensure your database is accessible from your deployment (check firewall rules)
  • Verify your endpoint URL is publicly accessible - Check the webhook status in your Resend Dashboard - Ensure your server responds with HTTP 200 for successful requests

Learn More