How to subscribe to webhook notifications on 100Pay

How to subscribe to webhook notifications on 100Pay

In this article, I will show you how to enable webhooks on your 100pay account and listen for new payment notifications.

100Pay Webhook Integration Guide

For 100Pay Checkout documentation, please click here šŸ‘‰ 100Pay checkout pop-up SDK

For API documentation, please click here šŸ‘‰ 100Pay Developers API documentation

Table of Contents

  1. Introduction to Webhooks
  2. Why Webhooks are Important
  3. Security Measures for Webhooks
  4. Webhook Integration Steps
    • Setting Up Webhook Endpoint
    • Verifying Webhook Signatures
    • Processing Webhook Data and Updating Database
  5. Example Code Snippets
    • JavaScript Example
    • Node.js Example

1. Introduction to Webhooks

Webhooks are a way for external systems to provide real-time information to your application by sending HTTP POST requests to a designated URL, also known as the webhook endpoint. Webhooks are commonly used for event-driven architectures, such as notifying your application about payment updates, order status changes, or other important events.

2. Why Webhooks are Important

Webhooks enable your application to receive immediate updates without the need to repeatedly poll an API for changes. This leads to more efficient resource utilization and faster response times.

3. Security Measures for Webhooks

To ensure the security and authenticity of webhook data, consider implementing the following measures:

  • HTTPS: Use HTTPS for your webhook endpoint to encrypt data during transmission.
  • Signature Verification: Include a signature in the webhook payload and verify it to ensure the data is from 100Pay.
  • Secret Key: Use a shared secret key to generate and verify signatures.
  • IP Whitelisting: Whitelist IP addresses from 100Pay's servers to prevent unauthorized requests.

4. Webhook Integration Steps

Setting Up Webhook Endpoint

  1. In your application, create an endpoint to receive webhook notifications. This endpoint should be accessible via HTTPS.

Verifying Webhook Signatures

  1. When you receive a webhook request, extract the provided signature and payload.
  2. Use your secret key to re-calculate the signature based on the payload.
  3. Compare the calculated signature with the received signature to verify authenticity.

Processing Webhook Data and Updating Database

  1. Parse the JSON payload to extract relevant data, such as payment status, amount, and currency.
  2. Compare the received amount and currency with your expected values to ensure consistency.
  3. Update your database with the payment status and other relevant information.

āš ļø To enable webhook šŸŖ notifications on your 100Pay account, log into your account and click on settings I'm the sidebar menu, next enter your web hook url and your secret key. click on save and that's it.

below are some example codes you can copy and paste. If you should have any issue, feel free to reachout to me.

5. Example Code Snippets

Example JSON response


{
    "charge": {
        "customer": {
            "user_id": "1",
            "name": "Brainy Josh",
            "phone": "08066427168",
            "email": "[email protected]"
        },
        "billing": {
            "currency": "NGN",
            "vat": 0,
            "pricing_type": "fixed_price",
            "amount": "600",
            "description": "Payment for your Shop100 Order",
            "country": "NG"
        },
        "status": {
            "context": {
                "status": "overpaid",
                "value": 491.45
            },
            "value": "overpaid",
            "total_paid": 1091.45
        },
        "ref_id": "966795153",
        "payments": [
            {
                "network": "algorand",
                "transaction_id": "JOVSHQ6XZSAXE7A6JWUV4H34ODNDTEVHIKZJANIAOOD6ZH5K452A",
                "status": "CONFIRMED",
                "timestamp": "2023-03-29T03:04:39.615Z",
                "value": {
                    "local": {
                        "amount": 155.92384371911047,
                        "currency": "NGN"
                    },
                    "crypto": {
                        "amount": 1,
                        "currency": "ALGO"
                    }
                },
                "block": {
                    "height": null,
                    "hash": "JOVSHQ6XZSAXE7A6JWUV4H34ODNDTEVHIKZJANIAOOD6ZH5K452A"
                }
            },
            {
                "network": "algorand",
                "transaction_id": "POKAKNBB3ZOXC7MK2QTXR65UA3R4OYDGDV2RWFHQP4YYZ5DKGHAQ",
                "status": "CONFIRMED",
                "timestamp": "2023-03-29T03:04:54.636Z",
                "value": {
                    "local": {
                        "amount": 311.84768743822093,
                        "currency": "NGN"
                    },
                    "crypto": {
                        "amount": 2,
                        "currency": "ALGO"
                    }
                },
                "block": {
                    "height": null,
                    "hash": "POKAKNBB3ZOXC7MK2QTXR65UA3R4OYDGDV2RWFHQP4YYZ5DKGHAQ"
                }
            },
            {
                "network": "algorand",
                "transaction_id": "JHT3RK2KQHMLKA24QR7JLPWXIMX7O2J4KKF7TL2I3NRVLC7RDELQ",
                "status": "CONFIRMED",
                "timestamp": "2023-03-29T03:09:38.251Z",
                "value": {
                    "local": {
                        "amount": 623.681562705202,
                        "currency": "NGN"
                    },
                    "crypto": {
                        "amount": 4,
                        "currency": "ALGO"
                    }
                },
                "block": {
                    "height": null,
                    "hash": "JHT3RK2KQHMLKA24QR7JLPWXIMX7O2J4KKF7TL2I3NRVLC7RDELQ"
                }
            }
        ],
        "charge_source": "external",
        "createdAt": "2023-03-29T02:16:13.825Z",
        "_id": "64239ff59b4e7700379c2106",
        "metadata": {
            "order_id": "64239ff4d1460e004b199e04",
            "charge_ref": "64239ff4d1460e004b199e04",
            "customer_id": "622f8f67f8c81600420d7769",
            "store_id": "62bee01ccbec51004d585ee1",
            "store_email": "[email protected]"
        },
        "call_back_url": "https://web.shop100.store/thank-you-page/",
        "app_id": "642367e95f09730038029f87",
        "userId": "642366ba5f09730038029f55",
        "chargeId": "64239ff59b4e7700379c2106",
        "__v": 0
    },
    "new_payment": {
        "network": "algorand",
        "transaction_id": "JHT3RK2KQHMLKA24QR7JLPWXIMX7O2J4KKF7TL2I3NRVLC7RDELQ",
        "status": "CONFIRMED",
        "timestamp": "2023-03-29T03:09:38.251Z",
        "value": {
            "local": {
                "amount": "623.68",
                "currency": "NGN"
            },
            "crypto": {
                "amount": 4,
                "currency": "ALGO"
            }
        },
        "block": {
            "hash": "JHT3RK2KQHMLKA24QR7JLPWXIMX7O2J4KKF7TL2I3NRVLC7RDELQ"
        }
    }
}

The above JSON data sample is for reference purposes only. The data 100pay will send to your server will be in this exact format.

Node.js Example


const express = require('express');
const mongoose = require('mongoose');

const app = express();
const YOUR_SECRET_KEY = 'your_secret_key';
const MONGO_URI = 'mongodb://localhost:27017/your_database_name';

// Define a mongoose model for payments
const Payment = mongoose.model('Payment', {
    transaction_id: String,
    network: String,
    status: String,
    timestamp: Date,
    value: {
        local: {
            amount: Number,
            currency: String
        },
        crypto: {
            amount: Number,
            currency: String
        }
    }
});

mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });

async function isPaymentProcessed(transactionId) {
    const existingPayment = await Payment.findOne({ transaction_id: transactionId });
    return existingPayment !== null;
}

async function updateDatabase(payload) {
    const newPaymentTransactionId = payload.new_payment.transaction_id;

    // Check whether newPaymentTransactionId already exists in the database
    if (!await isPaymentProcessed(newPaymentTransactionId)) {
        const payment = new Payment(payload.new_payment);
        await payment.save();
        console.log(`Payment with transaction ID ${newPaymentTransactionId} processed and logged.`);
    } else {
        console.log(`Payment with transaction ID ${newPaymentTransactionId} already processed.`);
    }

    // Update charge status in the database based on the "value" field
    const chargeStatus = payload.charge.status.value;
    if (chargeStatus === 'paid') {
        console.log('Charge status updated: Paid');
        // Update the charge status in the database to "Paid"
    } else if (chargeStatus === 'overpaid' || chargeStatus === 'underpaid') {
        const overpaidValue = payload.charge.status.context.value;
        console.log(`Charge status updated: ${chargeStatus.charAt(0).toUpperCase() + chargeStatus.slice(1)}. Over/underpaid amount: ${overpaidValue}`);
        // Update the charge status in the database to "Overpaid" or "Underpaid"
    }
}

app.use(express.json());

app.post('/webhook', async (req, res) => {
    const receivedToken = req.headers['verification-token'];

    if (receivedToken === YOUR_SECRET_KEY) {
        const payload = req.body;

        // Verify the webhook request
        if (await isPaymentProcessed(payload.new_payment.transaction_id)) {
            await updateDatabase(payload);
            res.send('Webhook received and payment processed');
        } else {
            res.status(400).send('Payment already processed or invalid');
        }
    } else {
        res.status(403).send('Webhook verification failed');
    }
});

app.listen(3000, () => {
    console.log('Webhook server started on port 3000');
});

The above sample code is writen for a nodejs environment. You may have to adjust it to meet your project goals.

PHP and mysql Example

This PHP script handles the webhook request, verifies it, checks if the payment is processed, and updates the MySQL database accordingly. It echoes messages to indicate the processing steps.

Feel free to use this code as a reference for handling webhooks and MySQL database operations in PHP, and if you have any further questions or need additional assistance, please let me know!

Here you go:
<?php

$YOUR_SECRET_KEY = 'your_secret_key';
$MYSQL_HOST = 'localhost';
$MYSQL_USER = 'your_mysql_user';
$MYSQL_PASSWORD = 'your_mysql_password';
$MYSQL_DATABASE = 'your_database_name';

$connection = mysqli_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD, $MYSQL_DATABASE);

function isPaymentProcessed($transactionId) {
    global $connection;
    $query = "SELECT * FROM payments WHERE transaction_id = '$transactionId'";
    $result = mysqli_query($connection, $query);
    return mysqli_num_rows($result) > 0;
}

function updateDatabase($payload) {
    global $connection;

    $newPaymentTransactionId = $payload['new_payment']['transaction_id'];

    if (!isPaymentProcessed($newPaymentTransactionId)) {
        $localAmount = $payload['new_payment']['value']['local']['amount'];
        $localCurrency = $payload['new_payment']['value']['local']['currency'];
        $query = "INSERT INTO payments (transaction_id, local_amount, local_currency) VALUES ('$newPaymentTransactionId', $localAmount, '$localCurrency')";
        mysqli_query($connection, $query);
        echo "Payment with transaction ID $newPaymentTransactionId processed and logged.\n";
    } else {
        echo "Payment with transaction ID $newPaymentTransactionId already processed.\n";
    }

    $chargeStatus = $payload['charge']['status']['value'];
    if ($chargeStatus === 'paid') {
        echo "Charge status updated: Paid\n";
    } elseif ($chargeStatus === 'overpaid' || $chargeStatus === 'underpaid') {
        $overpaidValue = $payload['charge']['status']['context']['value'];
        echo "Charge status updated: " . ucfirst($chargeStatus) . ". Over/underpaid amount: $overpaidValue\n";
    }
}

$payload = json_decode(file_get_contents('php://input'), true);
$receivedToken = $_SERVER['HTTP_VERIFICATION_TOKEN'];

if ($receivedToken === $YOUR_SECRET_KEY) {
    if (isPaymentValid($payload)) {
        updateDatabase($payload);
        echo "Webhook received, verified, and payment processed\n";
    } else {
        http_response_code(400);
        echo "Payment already processed or invalid\n";
    }
} else {
    http_response_code(403);
    echo "Webhook verification failed\n";
}

mysqli_close($connection);

?>

Please replace 'your_secret_key', 'your_mysql_user', 'your_mysql_password', and 'your_database_name' with the appropriate values.

go and Mongodb example

I've also included an example implementation in go. please adjust it to meet your project goals.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

const (
	yourSecretKey = "your_secret_key"
	mongoURI      = "mongodb://localhost:27017"
	databaseName  = "your_database_name"
	collectionName = "payments"
)

type Payment struct {
	TransactionID string    `bson:"transaction_id"`
	Network       string    `bson:"network"`
	Status        string    `bson:"status"`
	Timestamp     time.Time `bson:"timestamp"`
	Value         struct {
		Local struct {
			Amount   float64 `bson:"amount"`
			Currency string  `bson:"currency"`
		} `bson:"local"`
		Crypto struct {
			Amount   float64 `bson:"amount"`
			Currency string  `bson:"currency"`
		} `bson:"crypto"`
	} `bson:"value"`
}

func isPaymentProcessed(transactionID string, collection *mongo.Collection) bool {
	filter := bson.M{"transaction_id": transactionID}
	count, err := collection.CountDocuments(nil, filter)
	if err != nil {
		log.Println("Error checking payment:", err)
		return false
	}
	return count > 0
}

func updateDatabase(payload map[string]interface{}, collection *mongo.Collection) {
	payment := payload["new_payment"].(map[string]interface{})
	transactionID := payment["transaction_id"].(string)

	if !isPaymentProcessed(transactionID, collection) {
		_, err := collection.InsertOne(nil, payment)
		if err != nil {
			log.Println("Error inserting payment:", err)
			return
		}
		fmt.Printf("Payment with transaction ID %s processed and logged.\n", transactionID)
	} else {
		fmt.Printf("Payment with transaction ID %s already processed.\n", transactionID)
	}

	chargeStatus := payload["charge"].(map[string]interface{})["status"].(map[string]interface{})["value"].(string)
	if chargeStatus == "paid" {
		fmt.Println("Charge status updated: Paid")
	} else if chargeStatus == "overpaid" || chargeStatus == "underpaid" {
		overpaidValue := payload["charge"].(map[string]interface{})["status"].(map[string]interface{})["context"].(map[string]interface{})["value"].(float64)
		fmt.Printf("Charge status updated: %s. Over/underpaid amount: %f\n", chargeStatus, overpaidValue)
	}
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	receivedToken := r.Header.Get("Verification-Token")

	if receivedToken == yourSecretKey {
		var payload map[string]interface{}
		decoder := json.NewDecoder(r.Body)
		if err := decoder.Decode(&payload); err != nil {
			http.Error(w, "Invalid payload", http.StatusBadRequest)
			return
		}

		client, err := mongo.Connect(nil, options.Client().ApplyURI(mongoURI))
		if err != nil {
			log.Fatal(err)
		}
		defer client.Disconnect(nil)

		db := client.Database(databaseName)
		collection := db.Collection(collectionName)

		if isPaymentValid(payload) {
			updateDatabase(payload, collection)
			w.WriteHeader(http.StatusOK)
			fmt.Fprintln(w, "Webhook received, verified, and payment processed")
		} else {
			w.WriteHeader(http.StatusBadRequest)
			fmt.Fprintln(w, "Payment already processed or invalid")
		}
	} else {
		w.WriteHeader(http.StatusForbidden)
		fmt.Fprintln(w, "Webhook verification failed")
	}
}

func isPaymentValid(payload map[string]interface{}) bool {
	// Implement payment validation logic here based on payload
	return true
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/webhook", webhookHandler).Methods("POST")

	serverAddr := "localhost:3000"
	fmt.Printf("Webhook server started on %s\n", serverAddr)
	log.Fatal(http.ListenAndServe(serverAddr, router))
}

Please replace "your_secret_key", "your_database_name", and implement the isPaymentValid function for your specific validation logic.

This Go script uses the Gorilla Mux router and the MongoDB Go driver for handling webhooks and interacting with the MongoDB database.

Feel free to use this code as a reference for handling webhooks and MongoDB operations in Go, and if you have any further questions or need additional assistance, please let me know!

This technical documentation provides a comprehensive guide to integrating 100Pay's webhooks into your application. It covers the importance of webhooks, security considerations, and step-by-step instructions along with example code snippets in JavaScript and Node.js. By following these guidelines, you can effectively receive and process webhook notifications, ensuring accurate payment updates and database management.

If you have any further questions or need assistance, feel free to reach out to our support team at [email protected].

;