EPaySe
Testing

Testing Webhooks

Test and debug your webhook integration before going live

Test Mode vs Live Mode

Webhooks in sandbox environment have livemode: false in the payload

The livemode Field

Check the livemode field in the payload to distinguish between test and production webhooks

livemode: false
Test/Sandbox
livemode: true
Production

Send Test Webhook

Send a test webhook directly from your dashboard to verify your endpoint

1

Open Settings

Go to Settings → Webhooks in your merchant dashboard

2

Select Webhook

Click on the webhook configuration you want to test

3

Send Test

Click the "Send Test" button and check the response

Local Development

Test webhooks on your local machine using tunnel services like ngrok

ngrok Setup

Expose your local server to the internet with ngrok

Bash
ngrok Setup
# Install ngrok
brew install ngrok  # macOS
# or download from https://ngrok.com/download

# Authenticate (one-time)
ngrok config add-authtoken YOUR_AUTH_TOKEN

# Start tunnel to your local server
ngrok http 8000

# You'll get a URL like: https://abc123.ngrok.io
# Use this URL as your webhook endpoint

Testing with cURL

Test your webhook endpoint manually by generating signed requests with cURL

Generate Test Signature

Bash
Generate Test Signature
# Generate HMAC-SHA256 signature for testing
#!/bin/bash

TIMESTAMP=$(date +%s)
PAYLOAD='{"id":"evt_test_123","type":"test.webhook"}'
SECRET="your_webhook_secret"

# Build signed payload
SIGNED_PAYLOAD="${TIMESTAMP}.${PAYLOAD}"

# Generate signature
SIGNATURE=$(echo -n "$SIGNED_PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

echo "X-Webhook-Timestamp: $TIMESTAMP"
echo "X-Webhook-Signature: $SIGNATURE"

Send Test Request

Bash
Send Test Webhook via cURL
# Send a test webhook to your local endpoint
curl -X POST http://localhost:8000/webhooks/epayse \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: your_computed_signature" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -H "X-Webhook-Event-Id: evt_test_123" \
  -H "X-Webhook-Event-Type: test.webhook" \
  -d '{
    "id": "evt_test_123",
    "object": "event",
    "type": "test.webhook",
    "created": "2025-01-16T08:00:00Z",
    "livemode": false,
    "api_key_id": "client_test_123",
    "data": {
      "object": {
        "message": "Test webhook successful!"
      }
    },
    "api_version": "2024-01-01"
  }'

Unit Testing

Write automated tests to verify your webhook handler processes event types correctly

PHP
Laravel Feature Test Example
<?php
// tests/Feature/WebhookTest.php

namespace Tests\Feature;

use Tests\TestCase;

class WebhookTest extends TestCase
{
    public function test_handles_payment_succeeded_webhook(): void
    {
        $payload = [
            'id' => 'evt_test_123',
            'type' => 'payment.succeeded',
            'livemode' => false,
            'data' => [
                'object' => [
                    'id' => 'txn_test_123',
                    'amount' => 10000,
                    'status' => 'SUCCESS',
                ],
            ],
        ];

        $timestamp = time();
        $signedPayload = $timestamp . '.' . json_encode($payload);
        $signature = hash_hmac('sha256', $signedPayload, config('services.epayse.webhook_secret'));

        $response = $this->postJson('/webhooks/epayse', $payload, [
            'X-Webhook-Signature' => $signature,
            'X-Webhook-Timestamp' => $timestamp,
            'X-Webhook-Event-Id' => 'evt_test_123',
            'X-Webhook-Event-Type' => 'payment.succeeded',
        ]);

        $response->assertStatus(200);
        // Add more assertions...
    }

    public function test_rejects_invalid_signature(): void
    {
        $response = $this->postJson('/webhooks/epayse', [
            'id' => 'evt_test_123',
            'type' => 'payment.succeeded',
        ], [
            'X-Webhook-Signature' => 'invalid_signature',
            'X-Webhook-Timestamp' => time(),
        ]);

        $response->assertStatus(401);
    }
}

Debugging & Logging

Log incoming webhooks to debug verification and processing issues

PHP
Debug Logging Example
<?php
// Log incoming webhooks for debugging
public function handleWebhook(Request $request)
{
    // Log all incoming webhooks (remove in production)
    Log::channel('webhooks')->info('Webhook received', [
        'headers' => $request->headers->all(),
        'payload' => $request->all(),
        'ip' => $request->ip(),
    ]);

    // Verify signature...
    if (!$this->verifySignature($request)) {
        Log::channel('webhooks')->warning('Invalid webhook signature', [
            'signature' => $request->header('X-Webhook-Signature'),
            'timestamp' => $request->header('X-Webhook-Timestamp'),
        ]);
        return response('Invalid signature', 401);
    }

    // Process webhook...
    return response('OK', 200);
}

Retry Mechanism

Failed webhooks are automatically retried with exponential backoff

AttemptDelayTotal Time
Initial
Immediate0 seconds
Retry 1
30 seconds30 seconds
Retry 2
1 minute1.5 minutes
Retry 3
5 minutes6.5 minutes
Retry 4
30 minutes36.5 minutes
Retry 5
1 hour1.5 hours

Response Codes

How EPaySe interprets your webhook endpoint's response

2xx

Success

Webhook delivered and processed successfully

Behavior: No retry, marked as delivered

4xx

Client Error

Endpoint returned a 4xx error

Behavior: No retry (except 408, 429)

5xx

Server Error

Endpoint returned a 5xx error

Behavior: Will retry according to schedule

Timeout

Timeout

Endpoint did not respond within 30 seconds

Behavior: Will retry according to schedule

Testing Best Practices

Separate Environments

Use different webhook endpoints for sandbox and production

Log Everything

In development, log headers, payload, and verification results for easy debugging

Idempotent Processing

Test with duplicate webhooks to ensure your handler correctly handles already-processed events

Respond Quickly

Return 200 within 30 seconds, process asynchronously if more time is needed