---
title: "Webhook Quick Start Guide"
description: "Set up your first EdenPay webhook endpoint in under 5 minutes."
sidebar_position: 2
last_updated: 2026-08-21
---

# Webhook Quick Start Guide

Set up your first EdenPay webhook endpoint in under 5 minutes.

## Prerequisites

- Active bex.co account ([sign up here](https://bex.co/dash))
- 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 [bex.co dashboard](https://bex.co/dash)
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 <ion-icon name="warning" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#f59e0b"}}></ion-icon> **Save it securely** - you won't see it again

:::tip 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

```javascript
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('<ion-icon name="warning" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#f59e0b"}}></ion-icon> Invalid signature');
      return res.status(401).send('Unauthorized');
    }

    // 3. Parse and handle the event
    const event = JSON.parse(req.body.toString());
    console.log('<ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Received event:', event.type);

    switch (event.type) {
      case 'payment.confirmed':
        // Payment confirmed - safe to fulfill order
        console.log('<ion-icon name="cash" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Payment confirmed:', event.data.paymentId);
        // TODO: Your fulfillment logic here
        break;

      case 'payment.failed':
        // Payment failed - notify customer
        console.log('<ion-icon name="close-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#ef4444"}}></ion-icon> Payment failed:', event.data.paymentId);
        // TODO: Your failure handling logic here
        break;

      default:
        console.log('<ion-icon name="information-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#3b82f6"}}></ion-icon> Unhandled event type:', event.type);
    }

    // 4. Return 200 to acknowledge receipt
    res.status(200).json({ received: true });
  }
);

app.listen(3000, () => {
  console.log('<ion-icon name="headset" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#8b5cf6"}}></ion-icon> Webhook server listening on port 3000');
});
```

### Next.js App Router

```typescript
// 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('<ion-icon name="warning" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#f59e0b"}}></ion-icon> Invalid signature');
      return NextResponse.json(
        { error: 'Invalid signature' },
        { status: 401 }
      );
    }

    // 4. Parse and handle the event
    const event = JSON.parse(body);
    console.log('<ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Received event:', event.type);

    switch (event.type) {
      case 'payment.confirmed':
        console.log('<ion-icon name="cash" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Payment confirmed:', event.data.paymentId);
        // TODO: Your fulfillment logic here
        await fulfillOrder(event.data);
        break;

      case 'payment.failed':
        console.log('<ion-icon name="close-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#ef4444"}}></ion-icon> 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

```python
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('<ion-icon name="warning" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#f59e0b"}}></ion-icon> Invalid signature')
        return jsonify({'error': 'Unauthorized'}), 401

    # 3. Parse and handle the event
    event = request.get_json()
    print(f'<ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Received event: {event["type"]}')

    if event['type'] == 'payment.confirmed':
        print(f'<ion-icon name="cash" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Payment confirmed: {event["data"]["paymentId"]}')
        # TODO: Your fulfillment logic here

    elif event['type'] == 'payment.failed':
        print(f'<ion-icon name="close-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#ef4444"}}></ion-icon> 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 bex.co dashboard → Webhooks tab
3. **Click "Test"** on your webhook endpoint
4. **Check your server logs** to verify:
   - <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Event was received
   - <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Signature was validated correctly
   - <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> Event was processed successfully

:::tip Testing Locally
For local development, use tools like [ngrok](https://ngrok.com/) or [localtunnel](https://localtunnel.me/) to expose your local server to the internet:

```bash
# 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:

```javascript
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

- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Always verify signatures** before processing events
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Store secrets securely** in environment variables
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Use HTTPS only** - HTTP endpoints will be rejected
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Return 200 quickly** - process heavy tasks asynchronously
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Handle idempotency** - events may be delivered more than once
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **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

- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Production Ready**: [Signature Verification Guide](./signatures.md) for advanced security
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **All Events**: [Event Reference](./events.md) for complete event payload documentation
- <ion-icon name="checkmark-circle" style={{verticalAlign: "middle", fontSize: "1.2em", color: "#10b981"}}></ion-icon> **Debugging**: [Troubleshooting Guide](./troubleshooting.md) for common problems

## Need Help?

- **View Logs**: Check delivery attempts in the dashboard
- **Replay Events**: Use the "Replay" button for failed deliveries
- **Forum**: [https://bex.co/forum](https://bex.co/forum)
- **Support**: hello@bex.co
