Webhook Configuration
Webhooks enable your application to receive real-time notifications about payment events. DVPay sends HTTP POST requests to your configured webhook endpoint whenever specific events occur.
Setup Your Webhook
Step 1: Create an Endpoint
Create a publicly accessible HTTPS endpoint in your application to receive webhook events.
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type WebhookPayload struct {
Event string `json:"event"`
OrderID string `json:"order_id"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
Status string `json:"status"`
Timestamp int64 `json:"timestamp"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read the request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
// Parse webhook payload
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Process the webhook event
switch payload.Event {
case "payment.success":
fmt.Printf("Payment successful: %s\n", payload.OrderID)
// Update your database, send confirmation email, etc.
case "payment.failed":
fmt.Printf("Payment failed: %s\n", payload.OrderID)
// Handle failed payment
case "refund.completed":
fmt.Printf("Refund completed: %s\n", payload.OrderID)
// Update order status
}
// Respond with 200 OK
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"received"}`))
}
func main() {
http.HandleFunc("/webhooks/dvpay", webhookHandler)
fmt.Println("Webhook server listening on :8080")
http.ListenAndServe(":8080", nil)
}
const express = require('express');
const app = express();
// Middleware to parse JSON
app.use(express.json());
app.post('/webhooks/dvpay', (req, res) => {
const payload = req.body;
console.log('Received webhook event:', payload.event);
// Process webhook events
switch (payload.event) {
case 'payment.success':
console.log(`Payment successful: ${payload.order_id}`);
// Update your database, send confirmation email, etc.
break;
case 'payment.failed':
console.log(`Payment failed: ${payload.order_id}`);
// Handle failed payment
break;
case 'refund.completed':
console.log(`Refund completed: ${payload.order_id}`);
// Update order status
break;
default:
console.log(`Unknown event: ${payload.event}`);
}
// Always respond with 200 OK
res.status(200).json({ status: 'received' });
});
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Webhook server listening on port ${PORT}`);
});
Step 2: Configure in DVPay App
After creating your endpoint, register it in the DVPay merchant dashboard:
Open DVPay Mobile App
Navigate to your merchant dashboard in the DVPay mobile application.
Go to API Settings
Tap on Settings → API Configuration → Webhook Settings
Enter Your Webhook URL
Input your publicly accessible HTTPS endpoint (e.g., https://yourdomain.com/webhooks/dvpay)
Select Event Types
Choose which events you want to receive:
payment.success- Payment completed successfullypayment.failed- Payment attempt failedpayment.pending- Payment is awaiting confirmationrefund.completed- Refund processed successfullyorder.cancelled- Order was cancelled
Save Configuration
Click Save to activate your webhook endpoint.
Webhook Events
DVPay sends the following event types:
Payload Structure
All webhook events follow this JSON structure:
{
"event": "payment.success",
"order_id": "ord_abc123xyz",
"amount": 100.00,
"currency": "USD",
"status": "completed",
"payment_method": "khqr",
"timestamp": 1696435200,
"metadata": {
"store_id": "store-123",
"customer_phone": "+855123456789"
}
}
payment.success, payment.failed)USD or KHR)completed, failed, pending, refunded, cancelled)khqr, bank_transfer)Security Best Practices
Implement signature verification to ensure webhooks are sent by DVPay:
// Example: Verify webhook signature
expectedSignature := r.Header.Get("X-DVPay-Signature")
calculatedSignature := hmacSHA256(body, webhookSecret)
if expectedSignature != calculatedSignature {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
Store processed webhook event IDs to prevent duplicate processing:
const processedEvents = new Set();
if (processedEvents.has(payload.order_id)) {
return res.status(200).json({ status: 'already_processed' });
}
processedEvents.add(payload.order_id);
// Process the event...
Respond with 200 OK within 5 seconds. Process heavy tasks asynchronously:
// Immediately respond
res.status(200).json({ status: 'received' });
// Process asynchronously
processWebhookAsync(payload);
Testing Webhooks
Local Development with ngrok
Expose your local server for webhook testing:
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js
# Expose port 8080
ngrok http 8080
Use the generated ngrok URL (e.g., https://abc123.ngrok.io/webhooks/dvpay) in your webhook configuration.
Manual Testing
Test your webhook endpoint manually:
curl -X POST https://yourdomain.com/webhooks/dvpay \
-H "Content-Type: application/json" \
-d '{
"event": "payment.success",
"order_id": "test_order_123",
"amount": 50.00,
"currency": "USD",
"status": "completed",
"timestamp": 1696435200
}'
Retry Logic
If your webhook endpoint returns an error or doesn't respond within 5 seconds, DVPay will retry:
- 1st retry: After 1 minute
- 2nd retry: After 10 minutes
- 3rd retry: After 1 hour
- Final retry: After 6 hours
Troubleshooting
Common causes:
- Endpoint is not publicly accessible
- Using HTTP instead of HTTPS
- Firewall blocking DVPay IP addresses
- Server returning non-200 response
Solution: Test your endpoint with curl and check server logs
Cause: Network issues or slow response times trigger retries
Solution: Implement idempotency using event IDs or order IDs
Cause: Processing takes longer than 5 seconds
Solution: Respond immediately with 200 OK, then process asynchronously