Skip to main content

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)โ€‹

  1. Log in to your BlockEden dashboard
  2. Navigate to Accept Payments โ†’ Webhooks tab
  3. Click "Add Endpoint" button
  4. 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 and payment.failed)
    • Description: Optional description for your reference
  5. Click "Create Endpoint"
  6. 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โ€‹

  1. Deploy your webhook handler to your server
  2. Go back to the BlockEden dashboard โ†’ Webhooks tab
  3. Click "Test" on your webhook endpoint
  4. 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โ€‹

Need Help?โ€‹