March 2, 2026 - v1.4.0
Overview
This release introduces a major authentication refactor that enhances security for sensitive financial operations (refunds and payouts) while simplifying authentication for standard API endpoints. This is a breaking change affecting all API integrations.
Breaking Changes
1. Signature-Based Authentication for Refunds
What's Changed
Refund API now requires HMAC-SHA256 signature for authentication instead of X-App-Id and X-Timestamp headers.
Endpoint Affected:
POST /api/v1/payment-gateway/order/refund
New Required Headers:
| Header | Type | Required | Description |
|---|---|---|---|
X-Api-Key | string | Yes | Your API key (unchanged) |
X-Timestamp | string | Yes | Unix timestamp in seconds |
X-Signature | string | Yes | HMAC-SHA256 signature of request |
X-Idempotent-Key | string | Yes | Unique key to prevent duplicate refunds |
Removed Headers:
- ❌
X-App-Id- No longer required
Signature Generation Formula:
message = rawPayload + timestamp
signature = HMAC-SHA256(message, api_secret)
Example Request:
# Example values
TIMESTAMP="1709380800"
PAYLOAD='{"orderId":1234567890,"refundAmount":5.25}'
API_SECRET="your-api-secret"
# Generate signature
MESSAGE="${PAYLOAD}${TIMESTAMP}"
SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | cut -d' ' -f2)
# Make request
curl -X POST https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/refund \
-H "X-Api-Key: your-api-key" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-H "X-Idempotent-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"
Code Examples:
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/google/uuid"
)
type RefundRequest struct {
OrderID int64 `json:"orderId"`
RefundAmount float64 `json:"refundAmount"`
}
func generateSignature(secret, timestamp, rawPayload string) string {
// Create message: rawPayload + timestamp
message := rawPayload + timestamp
// Create HMAC with SHA256
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
// Return hex digest
return hex.EncodeToString(h.Sum(nil))
}
func refundOrder(orderID int64, amount float64, apiKey, apiSecret string) error {
url := "https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/refund"
// Create payload
payload := RefundRequest{
OrderID: orderID,
RefundAmount: amount,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
rawPayload := string(jsonData)
// Generate timestamp
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
// Generate signature
signature := generateSignature(apiSecret, timestamp, rawPayload)
// Generate idempotent key
idempotentKey := uuid.New().String()
// Create request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
// Add headers
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Signature", signature)
req.Header.Set("X-Idempotent-Key", idempotentKey)
req.Header.Set("Content-Type", "application/json")
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Printf("Response: %s\n", string(body))
return nil
}
func main() {
err := refundOrder(1234567890, 5.25, "your-api-key", "your-api-secret")
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}
const axios = require('axios');
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
function generateSignature(secret, timestamp, rawPayload) {
// Create HMAC with SHA256
const hmac = crypto.createHmac('sha256', secret);
// Create message: rawPayload + timestamp
const message = rawPayload + timestamp.toString();
// Update HMAC with message and get hex digest
hmac.update(message);
const signature = hmac.digest('hex');
return signature;
}
async function refundOrder(orderId, amount, apiKey, apiSecret) {
const url = 'https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/refund';
// Create payload
const payload = {
orderId: orderId,
refundAmount: amount
};
const rawPayload = JSON.stringify(payload);
// Generate timestamp
const timestamp = Math.floor(Date.now() / 1000);
// Generate signature
const signature = generateSignature(apiSecret, timestamp, rawPayload);
// Generate idempotent key
const idempotentKey = uuidv4();
// Make request
const headers = {
'X-Api-Key': apiKey,
'X-Timestamp': timestamp.toString(),
'X-Signature': signature,
'X-Idempotent-Key': idempotentKey,
'Content-Type': 'application/json'
};
try {
const response = await axios.post(url, payload, { headers });
console.log('Refund response:', response.data);
return response.data;
} catch (error) {
console.error('Error processing refund:', error.response?.data || error.message);
throw error;
}
}
// Usage
(async () => {
try {
await refundOrder(1234567890, 5.25, 'your-api-key', 'your-api-secret');
} catch (error) {
console.error('Failed to process refund');
}
})();
Implementation Reference
→ Refund Order API Documentation
2. Signature-Based Authentication for Payouts
What's Changed
Payout API now requires HMAC-SHA256 signature for authentication instead of X-App-Id and X-Timestamp headers.
Endpoint Affected:
POST /api/v1/payment-gateway/order/payout
New Required Headers:
| Header | Type | Required | Description |
|---|---|---|---|
X-Api-Key | string | Yes | Your API key (unchanged) |
X-Timestamp | string | Yes | Unix timestamp in seconds |
X-Signature | string | Yes | HMAC-SHA256 signature of request |
X-Idempotent-Key | string | Yes | Unique key to prevent duplicate payouts |
Removed Headers:
- ❌
X-App-Id- No longer required
Example Request:
curl -X POST https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/payout \
-H "X-Api-Key: your-api-key" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-H "X-Idempotent-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"accountNumber": "123456789",
"amount": 100,
"currency": "USD"
}'
Code Examples:
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/google/uuid"
)
type PayoutRequest struct {
AccountNumber string `json:"accountNumber"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
func generateSignature(secret, timestamp, rawPayload string) string {
message := rawPayload + timestamp
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return hex.EncodeToString(h.Sum(nil))
}
func processPayout(accountNumber string, amount float64, currency, apiKey, apiSecret string) error {
url := "https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/payout"
payload := PayoutRequest{
AccountNumber: accountNumber,
Amount: amount,
Currency: currency,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
rawPayload := string(jsonData)
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
signature := generateSignature(apiSecret, timestamp, rawPayload)
idempotentKey := uuid.New().String()
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Signature", signature)
req.Header.Set("X-Idempotent-Key", idempotentKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Printf("Response: %s\n", string(body))
return nil
}
func main() {
err := processPayout("123456789", 100.00, "USD", "your-api-key", "your-api-secret")
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}
const axios = require('axios');
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
function generateSignature(secret, timestamp, rawPayload) {
const hmac = crypto.createHmac('sha256', secret);
const message = rawPayload + timestamp.toString();
hmac.update(message);
return hmac.digest('hex');
}
async function processPayout(accountNumber, amount, currency, apiKey, apiSecret) {
const url = 'https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/payout';
const payload = {
accountNumber: accountNumber,
amount: amount,
currency: currency
};
const rawPayload = JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000);
const signature = generateSignature(apiSecret, timestamp, rawPayload);
const idempotentKey = uuidv4();
const headers = {
'X-Api-Key': apiKey,
'X-Timestamp': timestamp.toString(),
'X-Signature': signature,
'X-Idempotent-Key': idempotentKey,
'Content-Type': 'application/json'
};
try {
const response = await axios.post(url, payload, { headers });
console.log('Payout response:', response.data);
return response.data;
} catch (error) {
console.error('Error processing payout:', error.response?.data || error.message);
throw error;
}
}
// Usage
(async () => {
try {
await processPayout('123456789', 100.00, 'USD', 'your-api-key', 'your-api-secret');
} catch (error) {
console.error('Failed to process payout');
}
})();
Implementation Reference
3. Simplified Authentication for Standard Endpoints
What's Changed
All non-financial endpoints now only require X-Api-Key header for authentication. X-App-Id and X-Timestamp headers have been removed.
Endpoints Affected:
POST /api/v1/payment-gateway/order/create- Create OrderPOST /api/v1/payment-gateway/payment/generate-qr- Generate QR CodePOST /api/v1/payment-gateway/payment/transaction-detail- Get Transaction DetailsPOST /api/v1/payment-gateway/order/cancel- Cancel OrderPOST /api/v1/payment-gateway/balance-inquiry- Balance Inquiry
New Required Headers:
| Header | Type | Required | Description |
|---|---|---|---|
X-Api-Key | string | Yes | Your API key |
Removed Headers:
- ❌
X-App-Id- No longer required - ❌
X-Timestamp- No longer required
Before (v1.3.0 and earlier):
curl -X POST https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/create \
-H "X-App-Id: your-app-id" \
-H "X-Api-Key: your-api-key" \
-H "X-Timestamp: 1709380800" \
-H "Content-Type: application/json" \
-d '{"storeId":1234567890,"amount":10.5,"currency":"USD"}'
After (v1.4.0):
curl -X POST https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/create \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"storeId":1234567890,"amount":10.5,"currency":"USD"}'
Code Examples:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type CreateOrderRequest struct {
StoreID int64 `json:"storeId"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
func createOrder(storeID int64, amount float64, currency, apiKey string) error {
url := "https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/create"
payload := CreateOrderRequest{
StoreID: storeID,
Amount: amount,
Currency: currency,
}
jsonData, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
// Only X-Api-Key is required now
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Printf("Response: %s\n", string(body))
return nil
}
func main() {
err := createOrder(1234567890, 10.5, "USD", "your-api-key")
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}
const axios = require('axios');
async function createOrder(storeId, amount, currency, apiKey) {
const url = 'https://merchant.dv.vai247.pro/api/v1/payment-gateway/order/create';
const payload = {
storeId: storeId,
amount: amount,
currency: currency
};
// Only X-Api-Key is required now
const headers = {
'X-Api-Key': apiKey,
'Content-Type': 'application/json'
};
try {
const response = await axios.post(url, payload, { headers });
console.log('Order response:', response.data);
return response.data;
} catch (error) {
console.error('Error creating order:', error.response?.data || error.message);
throw error;
}
}
// Usage
(async () => {
try {
await createOrder(1234567890, 10.5, 'USD', 'your-api-key');
} catch (error) {
console.error('Failed to create order');
}
})();
What Will Break
❌ Requests with old headers will be rejected:
{
"error": {
"code": "invalid_authentication",
"message": "X-App-Id and X-Timestamp headers are no longer supported. Use X-Api-Key only.",
"request_id": "req_xyz789"
}
}
New Features
1. Webhook Signature Verification
What's New
DVPay now includes X-Signature header in all webhook requests, allowing you to verify webhook authenticity using your API secret.
Webhook Headers:
| Header | Type | Description |
|---|---|---|
X-Signature | string | HMAC-SHA256 signature of webhook payload |
Verification Formula:
message = rawPayload + timestamp (from createTimeMilli)
signature = HMAC-SHA256(message, api_secret)
Example Webhook Payload:
{
"accountName": "testPg0",
"accountNumber": "838825694",
"amount": 0.05,
"appId": 9328657108474192,
"createTimeMilli": 1772453630058,
"currency": "USD",
"orderId": 779539349308101,
"status": "SUCCESS",
"transactionId": 779539365712584
}
Verification Code Examples:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
)
type WebhookPayload struct {
AccountName string `json:"accountName"`
AccountNumber string `json:"accountNumber"`
Amount float64 `json:"amount"`
AppID int64 `json:"appId"`
CreateTimeMilli int64 `json:"createTimeMilli"`
Currency string `json:"currency"`
OrderID int64 `json:"orderId"`
Status string `json:"status"`
TransactionID int64 `json:"transactionId"`
}
func verifyWebhookSignature(rawPayload, timestamp, signature, apiSecret string) bool {
// Create message: rawPayload + timestamp
message := rawPayload + timestamp
// Generate expected signature
h := hmac.New(sha256.New, []byte(apiSecret))
h.Write([]byte(message))
expectedSignature := hex.EncodeToString(h.Sum(nil))
// Compare signatures
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Read raw 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()
rawPayload := string(body)
// Parse payload
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Get signature from header
receivedSignature := r.Header.Get("X-Signature")
if receivedSignature == "" {
http.Error(w, "Missing X-Signature header", http.StatusUnauthorized)
return
}
// Convert timestamp to seconds
timestamp := strconv.FormatInt(payload.CreateTimeMilli/1000, 10)
// Verify signature
apiSecret := "your-api-secret"
if !verifyWebhookSignature(rawPayload, timestamp, receivedSignature, apiSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process webhook
fmt.Printf("Verified webhook - Order ID: %d, Status: %s\n", payload.OrderID, payload.Status)
// Respond
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 crypto = require('crypto');
const app = express();
// Use raw body parser for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
function verifyWebhookSignature(rawPayload, timestamp, signature, apiSecret) {
// Create message: rawPayload + timestamp
const message = rawPayload + timestamp.toString();
// Generate expected signature
const hmac = crypto.createHmac('sha256', apiSecret);
hmac.update(message);
const expectedSignature = hmac.digest('hex');
// Compare signatures (constant-time comparison)
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/dvpay', (req, res) => {
const rawPayload = req.rawBody;
const payload = req.body;
// Get signature from header
const receivedSignature = req.headers['x-signature'];
if (!receivedSignature) {
return res.status(401).json({ error: 'Missing X-Signature header' });
}
// Convert timestamp to seconds
const timestamp = Math.floor(payload.createTimeMilli / 1000);
// Verify signature
const apiSecret = 'your-api-secret';
if (!verifyWebhookSignature(rawPayload, timestamp, receivedSignature, apiSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
console.log(`Verified webhook - Order ID: ${payload.orderId}, Status: ${payload.status}`);
// Handle different statuses
switch (payload.status) {
case 'SUCCESS':
console.log(`Payment successful - Order ID: ${payload.orderId}`);
// Update database, send confirmation, etc.
break;
case 'FAILED':
console.log(`Payment failed - Order ID: ${payload.orderId}`);
break;
}
// Respond
res.status(200).json({ status: 'received' });
});
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Webhook server listening on port ${PORT}`);
});
Implementation Reference
→ Webhook Configuration - Signature Verification
Documentation Updates
All relevant documentation has been updated to reflect the authentication changes:
Migration Timeline
March 2, 2026 (Today)
Grace period begins. Both old and new authentication methods accepted.
March 9, 2026
Deprecation warnings in response headers. All responses include X-DVPay-Deprecation-Warning header.
March 16, 2026
Enforcement begins. Old authentication headers rejected with 401 Unauthorized.
March 23, 2026
Full enforcement. All merchants must have updated integrations.
Migration Checklist
Before March 16, 2026, complete these steps:
Review Integration
Identify all API endpoints you're using
Obtain API Secret
Log in to DVPay mobile app → Settings → API Configuration → Copy API Secret
Update Refund Functions
Implement signature generation for refund API
Update Payout Functions
Implement signature generation for payout API
Simplify Standard Endpoints
Remove X-App-Id and X-Timestamp from create order, generate QR, transaction detail, and cancel order endpoints
Implement Webhook Verification
Add signature verification to your webhook handler
Test in Sandbox
Verify all endpoints work with new authentication
Deploy to Production
Roll out changes before March 16, 2026
Monitor Logs
Check for deprecation warnings and fix any missed instances
Security Benefits
This authentication refactor provides significant security improvements:
For Refund & Payout Operations:
- Tamper-proof requests: HMAC signatures prevent request tampering
- Replay attack protection: Timestamp validation prevents replay attacks
- No credential exposure: API secret never transmitted in requests
- Industry standard: HMAC-SHA256 is widely used for API security
For Standard Operations:
- Simplified integration: Single API key simplifies authentication
- Reduced attack surface: Fewer headers mean fewer potential vulnerabilities
- Better performance: Reduced header processing overhead
For Webhooks:
- Origin verification: Signature proves webhook came from DVPay
- Payload integrity: Ensures webhook data wasn't modified in transit
- Protection against spoofing: Prevents malicious actors from sending fake webhooks
API Secret Management
How to Get Your API Secret:
Open DVPay Mobile App
Launch the DVPay mobile application
Navigate to Settings
Tap Settings → API Configuration
View API Secret
Tap Show API Secret (requires PIN/biometric authentication)
Copy Secret
Copy the secret to a secure location
Best Practices:
- Store API secret in environment variables or secure secret managers
- Never hardcode API secret in source code
- Rotate API secret regularly (every 90 days recommended)
- Use different secrets for sandbox and production environments
Support
If you need assistance migrating your integration:
- Documentation: API Reference
- Email Support: support@dvpay.com
- Developer Forum: https://forum.dvpay.com
Summary
This release introduces signature-based authentication for financial operations (refunds and payouts) and simplifies authentication for standard endpoints to API key only. Additionally, webhooks now include signatures for verification. All merchants must update their integrations before March 16, 2026 to avoid service disruption.
Action Required:
- Implement HMAC-SHA256 signature generation for refund and payout APIs
- Remove
X-App-IdandX-Timestampheaders from standard endpoints - Implement webhook signature verification for security