Webhook Quick Start Guide
Set up your first EdenPay webhook endpoint in under 5 minutes.
Prerequisites
- Active BlockEden.xyz account (sign up here)
- A publicly accessible HTTPS server endpoint
- Basic knowledge of your backend framework
Step 1: Create a Webhook Endpoint (2 minutes)
- Log in to your BlockEden dashboard
- Navigate to Accept Payments → Webhooks tab
- Click "Add Endpoint" button
- Configure your endpoint:
- URL: Enter your HTTPS webhook URL (e.g.,
https://your-domain.com/api/webhooks
) - Events: Select events to receive (start with
payment.confirmed
andpayment.failed
) - Description: Optional description for your reference
- URL: Enter your HTTPS webhook URL (e.g.,
- Click "Create Endpoint"
- Copy the secret displayed in the success modal ⚠️ Save it securely - you won't see it again
Store Your Secret Securely
Save the webhook secret in your environment variables or secrets manager immediately. You'll need it to verify webhook signatures.
Step 2: Implement Your Webhook Handler (3 minutes)
Choose your backend framework:
Node.js with Express
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // Your webhook secret
// IMPORTANT: Use raw body for signature verification
app.post('/api/webhooks',
express.raw({ type: 'application/json' }),
(req, res) => {
// 1. Get signature from headers
const signature = req.headers['x-eden-signature'];
// 2. Verify signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
if (signature !== expectedSignature) {
console.error('⚠️ Invalid signature');
return res.status(401).send('Unauthorized');
}
// 3. Parse and handle the event
const event = JSON.parse(req.body.toString());
console.log('✅ Received event:', event.type);
switch (event.type) {
case 'payment.confirmed':
// Payment confirmed - safe to fulfill order
console.log('💰 Payment confirmed:', event.data.paymentId);
// TODO: Your fulfillment logic here
break;
case 'payment.failed':
// Payment failed - notify customer
console.log('❌ Payment failed:', event.data.paymentId);
// TODO: Your failure handling logic here
break;
default:
console.log('ℹ️ Unhandled event type:', event.type);
}
// 4. Return 200 to acknowledge receipt
res.status(200).json({ received: true });
}
);
app.listen(3000, () => {
console.log('🎧 Webhook server listening on port 3000');
});
Next.js App Router
// app/api/webhooks/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
export async function POST(req: NextRequest) {
try {
// 1. Get signature from headers
const signature = req.headers.get('x-eden-signature');
if (!signature) {
return NextResponse.json(
{ error: 'Missing signature' },
{ status: 401 }
);
}
// 2. Get raw body for signature verification
const body = await req.text();
// 3. Verify signature
const expectedSignature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(body)
.digest('hex');
if (signature !== expectedSignature) {
console.error('⚠️ Invalid signature');
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 401 }
);
}
// 4. Parse and handle the event
const event = JSON.parse(body);
console.log('✅ Received event:', event.type);
switch (event.type) {
case 'payment.confirmed':
console.log('💰 Payment confirmed:', event.data.paymentId);
// TODO: Your fulfillment logic here
await fulfillOrder(event.data);
break;
case 'payment.failed':
console.log('❌ Payment failed:', event.data.paymentId);
// TODO: Your failure handling logic here
await handleFailedPayment(event.data);
break;
}
// 5. Return 200 to acknowledge receipt
return NextResponse.json({ received: true }, { status: 200 });
} catch (error) {
console.error('Error processing webhook:', error);
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: 500 }
);
}
}
// Example fulfillment function
async function fulfillOrder(paymentData: any) {
// Your business logic here
// - Update database
// - Send confirmation email
// - Grant access to purchased content
}
async function handleFailedPayment(paymentData: any) {
// Your failure handling logic here
// - Send failure notification
// - Update order status
}
Python with Flask
from flask import Flask, request, jsonify
import hmac
import hashlib
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
@app.route('/api/webhooks', methods=['POST'])
def webhook():
# 1. Get signature from headers
signature = request.headers.get('x-eden-signature')
# 2. Verify signature
body = request.get_data()
expected_signature = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
body,
hashlib.sha256
).hexdigest()
if signature != expected_signature:
print('⚠️ Invalid signature')
return jsonify({'error': 'Unauthorized'}), 401
# 3. Parse and handle the event
event = request.get_json()
print(f'✅ Received event: {event["type"]}')
if event['type'] == 'payment.confirmed':
print(f'💰 Payment confirmed: {event["data"]["paymentId"]}')
# TODO: Your fulfillment logic here
elif event['type'] == 'payment.failed':
print(f'❌ Payment failed: {event["data"]["paymentId"]}')
# TODO: Your failure handling logic here
# 4. Return 200 to acknowledge receipt
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
Step 3: Test Your Webhook
- Deploy your webhook handler to your server
- Go back to the BlockEden dashboard → Webhooks tab
- Click "Test" on your webhook endpoint
- Check your server logs to verify:
- ✅ Event was received
- ✅ Signature was validated correctly
- ✅ Event was processed successfully
Testing Locally
For local development, use tools like ngrok or localtunnel to expose your local server to the internet:
# With ngrok
ngrok http 3000
# With localtunnel
npx localtunnel --port 3000
Step 4: Handle Webhook Delivery
Return 200 Quickly
Your endpoint must return a 200 OK
response within 30 seconds. If processing takes longer, acknowledge receipt immediately and process asynchronously:
app.post('/api/webhooks', async (req, res) => {
// Verify signature...
// Return 200 immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(event).catch(console.error);
});
async function processWebhookAsync(event) {
// Your heavy processing here
// - Database operations
// - External API calls
// - Send emails
}
Monitor Delivery Attempts
View delivery attempts in the dashboard to:
- See request/response details
- Check error messages
- Replay failed events
- Monitor success rate
Security Checklist
- ✅ Always verify signatures before processing events
- ✅ Store secrets securely in environment variables
- ✅ Use HTTPS only - HTTP endpoints will be rejected
- ✅ Return 200 quickly - process heavy tasks asynchronously
- ✅ Handle idempotency - events may be delivered more than once
- ✅ Validate event structure before processing
Common Issues
401 Unauthorized
Cause: Signature verification is failing
Solutions:
- Verify you're using the correct webhook secret
- Ensure you're hashing the raw request body (before parsing)
- Check that your HMAC implementation matches (SHA-256)
Webhook Timeout
Cause: Your endpoint took longer than 30 seconds to respond
Solutions:
- Return 200 immediately and process asynchronously
- Move database operations to background queue
- Optimize slow API calls
Missing Events
Cause: Your endpoint is not publicly accessible
Solutions:
- Verify your endpoint is reachable from the internet
- Check firewall rules and security groups
- Test with
curl
from an external server
Next Steps
- ✅ Production Ready: Signature Verification Guide for advanced security
- ✅ All Events: Event Reference for complete event payload documentation
- ✅ Debugging: Troubleshooting Guide for common problems