๐Ÿ‘จโ€๐Ÿ’ปPayment Notification

SECURITY TIPS TO PROTECT YOUR WEBHOOK ENDPOINT

  1. Verify Payvessel Hash Signature:

    When receiving data from a webhook, it's crucial to ensure the data hasn't been tampered with during transmission. To achieve this, follow these steps:

    • Retrieve Payvessel Signature: Extract the Payvessel signature from the request's metadata. In most cases, this will be available in the HTTP_PAYVESSEL_HTTP_SIGNATURE header.

    • Generate Hash for Payload: Use your secret key as the key for an HMAC (Hash-based Message Authentication Code) with the SHA-512 algorithm. The payload of the webhook request is used as the message input for this HMAC function. This will generate a hash value.

    • Compare Hashes: Compare the generated hash with the Payvessel signature received in the request's metadata. If they match, it indicates that the data hasn't been tampered with, ensuring the authenticity of the webhook request.

  2. Verify Payvessel IP Address:

    To enhance security further, you can validate that the incoming webhook request originates from a trusted Payvessel server. Here's how:

    • Retrieve IP Address: Obtain the IP address of the incoming webhook request. You can usually find this in the REMOTE_ADDR field of the request's metadata.

    • Compare IP Address: Check if the IP address matches a predefined list of trusted Payvessel server IP addresses. In your case, it appears that the trusted IP address are "3.255.23.38", "162.246.254.36." If the IP address doesn't match, it may be an unauthorized request, and you should reject it.

  3. Prevent Duplicate Transactions:

    Webhooks can sometimes be delivered multiple times due to network issues or retries. To avoid processing the same transaction multiple times:

    • Transaction History Check: Before processing the webhook data, query your payment transaction database to check if a transaction with the same reference or identifier already exists. If a matching transaction is found, it may indicate a duplicate request, and you can choose to ignore it or handle it as necessary.

By implementing these security tips, you can protect your webhook endpoint from tampered data, ensure that requests come from trusted sources, and prevent duplicate processing of transactions, ultimately enhancing the security and reliability of your webhook integration with Payvessel.

PYTHON DJANGO SAMPLE WEBHOOK

@require_POST
@csrf_exempt
def payvessel_payment_done(request):
        payload = request.body
        payvessel_signature = request.META.get('HTTP_PAYVESSEL_HTTP_SIGNATURE')
        #this line maybe be differ depends on your server
        #ip_address = u'{}'.format(request.META.get('HTTP_X_FORWARDED_FOR'))
        ip_address = u'{}'.format(request.META.get('REMOTE_ADDR'))
        secret = bytes("PVSECRET-", 'utf-8')
        hashkey = hmac.new(secret,request.body, hashlib.sha512).hexdigest()
        ipAddress = ["3.255.23.38", "162.246.254.36"];
        if payvessel_signature == hashkey  and ip_address in ipAddress:
                data = json.loads(payload)
                amount = float(data['order']["amount"])
                settlementAmount = float(data['order']["settlement_amount"])
                fee = float(data['order']["fee"])
                reference = data['transaction']["reference"]
                description = data['order']["description"]
                settlementAmount = settlementAmount 
                
                ###check if reference already exist in your payment transaction table   
                if not paymentgateway.objects.filter(reference=reference).exists():
                   
                    #fund user wallet here
                    return JsonResponse({"message": "success",},status=200) 
                        
                else:
                    return JsonResponse({"message": "transaction already exist",},status=200) 
        
        else:
            return JsonResponse({"message": "Permission denied, invalid hash or ip address.",},status=400) 

PHP SAMPLE WEBHOOK CODE

<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // CSRF exemption

    $payload = file_get_contents('php://input');
    $payvessel_signature = $_SERVER['HTTP_PAYVESSEL_HTTP_SIGNATURE'];
    //this line maybe be differ depends on your server
    //$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; 
    $ip_address = $_SERVER['REMOTE_ADDR']; 
    $secret = "PVSECRET-";
    $hashkey = hash_hmac('sha512', $payload, $secret);
    $ipAddress = ["3.255.23.38", "162.246.254.36"];
     
     
    if ($payvessel_signature == $hashkey && in_array($ip_address, $ipAddress)) {
        $data = json_decode($payload, true);
        $amount = floatval($data['order']['amount']);
        $settlementAmount = floatval($data['order']['settlement_amount']);
        $fee = floatval($data['order']['fee']);
        $reference = $data['transaction']['reference'];
        $description = $data['order']['description'];
        $settlementAmount = $settlementAmount;

        // Check if reference already exists in your payment transaction table
        if (!paymentgateway::where('reference', $reference)->exists()) {
            // Fund user wallet here
            echo json_encode(["message" => "success"]);
            http_response_code(200);
        } else {
            echo json_encode(["message" => "transaction already exist"]);
            http_response_code(200);
        }
    } else {
        echo json_encode(["message" => "Permission denied, invalid hash or ip address."]);
        http_response_code(400);
    }
} else {
    // Handle other HTTP methods if needed
    echo json_encode(["message" => "Method not allowed"]);
    http_response_code(405);
}
?>

NODE JS SAMPLE

const crypto = require('crypto');
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
const port = 3000; // Replace with your desired port number

app.use(bodyParser.json());

app.post('/payvessel_payment_done', (req, res) => {
  const payload = req.body;
  const payvessel_signature = req.header('HTTP_PAYVESSEL_HTTP_SIGNATURE');
  const ip_address = req.connection.remoteAddress;
  const secret = 'PVSECRET-';

  const hash = crypto.createHmac('sha512', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  const ipAddress = ["3.255.23.38", "162.246.254.36"];
  
  if (payvessel_signature === hash && ipAddress.includes(ip_address)) {
    const data = payload;
    const amount = parseFloat(data.order.amount);
    const settlementAmount = parseFloat(data.order.settlement_amount);
    const fee = parseFloat(data.order.fee);
    const reference = data.transaction.reference;
    const description = data.order.description;

    // Check if reference already exists in your payment transaction table
    if (/* Check if reference exists */) {
      // Fund user wallet here
      res.status(200).json({ message: 'success' });
    } else {
      res.status(200).json({ message: 'transaction already exist' });
    }
  } else {
    res.status(400).json({ message: 'Permission denied, invalid hash or ip address.' });
  }
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Last updated