Versioning

March 2, 2026 - v1.4.0

Signature-based authentication for refund and payout operations, simplified authentication for standard endpoints
Authentication Refactor
Breaking change affecting all API endpoints - authentication headers simplified

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.

Action Required: All integrations must be updated before March 16, 2026. After this date, requests using old authentication headers will be rejected.

Breaking Changes

1. Signature-Based Authentication for Refunds

Breaking ChangeImpact: Critical

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:

HeaderTypeRequiredDescription
X-Api-KeystringYesYour API key (unchanged)
X-TimestampstringYesUnix timestamp in seconds
X-SignaturestringYesHMAC-SHA256 signature of request
X-Idempotent-KeystringYesUnique 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)
    }
}

Implementation Reference

Refund Order API Documentation


2. Signature-Based Authentication for Payouts

Breaking ChangeImpact: Critical

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:

HeaderTypeRequiredDescription
X-Api-KeystringYesYour API key (unchanged)
X-TimestampstringYesUnix timestamp in seconds
X-SignaturestringYesHMAC-SHA256 signature of request
X-Idempotent-KeystringYesUnique 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)
    }
}

Implementation Reference

Payout API Documentation


3. Simplified Authentication for Standard Endpoints

Breaking ChangeImpact: High

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 Order
  • POST /api/v1/payment-gateway/payment/generate-qr - Generate QR Code
  • POST /api/v1/payment-gateway/payment/transaction-detail - Get Transaction Details
  • POST /api/v1/payment-gateway/order/cancel - Cancel Order
  • POST /api/v1/payment-gateway/balance-inquiry - Balance Inquiry

New Required Headers:

HeaderTypeRequiredDescription
X-Api-KeystringYesYour 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)
    }
}

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

AdditionImpact: Medium

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:

HeaderTypeDescription
X-SignaturestringHMAC-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)
}

Implementation Reference

Webhook Configuration - Signature Verification


Documentation Updates

All relevant documentation has been updated to reflect the authentication changes:

Authentication

Complete rewrite for v1.4.0 authentication

Refund Order

Updated with signature-based auth

Payout

Updated with signature-based auth

Create Order

Simplified to X-Api-Key only

Webhook Configuration

Added signature verification guide

Security Best Practices

Updated with signature verification


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

Important: Your API secret is different from your API key. Keep it secure and never commit it to version control.

How to Get Your API Secret:

Open DVPay Mobile App

Launch the DVPay mobile application

Tap SettingsAPI 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:

Need extra time? Contact support@dvpay.com before March 10, 2026 to discuss your migration timeline.

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:

  1. Implement HMAC-SHA256 signature generation for refund and payout APIs
  2. Remove X-App-Id and X-Timestamp headers from standard endpoints
  3. Implement webhook signature verification for security