Skip to content
On this page

Setup webhooks ​

As a developer integrating with Polus Payments, you may want to receive real-time notifications about payment events that occur within the payment processing system. This is where webhooks come into play. Webhooks allow you to securely receive payment notifications on your server by providing a URL endpoint that Polus Payments will send these notifications to. Whenever a payment event occurs, Polus Payments will send a webhook to the specified endpoint with all the relevant details of the payment. With this information, you can automate your payment processing workflows and keep your records up-to-date in real-time, without having to manually check for payment updates.

Preparation ​

To start using webhooks with Polus Payments, there are a few preparation steps that need to be taken. Assuming you have already registered and obtained an API key, the first step is to prepare a public endpoint with mandatory TLS support. This endpoint will be where Polus Payments sends the webhook notifications. If you haven't yet registered and obtained an API key, please refer to the documentation section on Invoice creation for more information on how to get started.

Once you have a public endpoint ready, you can specify it in the merchant settings on the Polus Payments dashboard. When you saved settings, Polus Payments will send a confirmation HTTP POST request to your endpoint with http header x-verification: true. Your server must respond with the same request body - this step confirms that your webhook endpoint is set up correctly. After this, your server will start receiving notifications about new payment events, allowing you to take automated actions as needed.

Signature verification ​

Every webhook request sent by Polus Payments will include a x-signature header containing a hexadecimal representation of the signature. The recommended way to verify the signature is by using HMAC SHA512 with your API Key from the merchant settings as the key. To verify the signature, you should first extract the signature from the x-signature header, and then compute the HMAC SHA512 of the webhook payload using your API Key as the key. If the computed signature matches the signature from the x-signature header, then you can be confident that the webhook request is authentic and hasn't been tampered with. If the signatures don't match, you should reject the webhook request and investigate the issue further. Below are code examples:

WARNING

Please notice. It's important to verify the signature to ensure that the webhook request was sent by Polus Payments servers and hasn't been tampered with.

go
package main

import (
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

var SECRET_KEY []byte

func PaymentsWebhookHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	if r.Header.Get("x-verification") == "true" {
		// Poluspay sent webhook verification request
		body, err := ioutil.ReadAll(r.Body)
		if err != nil { // failed to read request body
			http.Error(w, "bad request", http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, string(body))
		return
	} else {
		// Poluspay sent payment notification
		mac := hmac.New(sha512.New, SECRET_KEY)
		mac.Write(body)
		sign := hex.EncodeToString(mac.Sum(nil))

		if xsign != sign { // invalid signature
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}

		// process the request...
		fmt.Fprintln(w, "request processed successfully")
	}
}

func main() {
	SECRET_KEY = []byte("secret123")
	const host string = "127.0.0.1:8080"

	log.Printf("server started on %s", host)
	http.HandleFunc("/payments-webhook", PaymentsWebhookHandler)
	log.Fatal(http.ListenAndServe(host, nil))
}
python
from flask import Flask, request
import hmac
import hashlib

app = Flask(__name__)

SECRET_KEY = bytes('secret123', 'utf-8')

@app.route('/payments-webhook', methods=['POST'])
def payments_webhook_handler():
    if request.method != 'POST':
        return 'method not allowed', 405

	if request.headers.get('x-verification') == "true":
		return request.get_data()
		
    xsign = request.headers.get('x-signature')
    if not xsign:
        return 'bad request', 400

    body = request.get_data()
    mac = hmac.new(SECRET_KEY, msg=body, digestmod=hashlib.sha512)
    sign = mac.hexdigest()

    if xsign != sign:
        return 'unauthorized', 401

    # process the request...
    return 'request processed successfully'

if __name__ == '__main__':
    host = '127.0.0.1'
    port = 8080

    print(f"server started on {host}:{port}")
    app.run(host='127.0.0.1', port=8080)
php
<?php

const SECRET_KEY = "secret123";

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    return http_response_code(405);
}

$headers = getallheaders();
if (array_key_exists("x-verification", $headers) && $headers['x-verification'] == 'true') {
	echo file_get_contents('php://input');
    return;
}

if (!array_key_exists("x-signature", $headers)) {
    return http_response_code(400);
}

$xsign = $headers["x-signature"];

$body = file_get_contents('php://input');
$sign = hash_hmac("sha512", $body, SECRET_KEY);
if ($sign != $xsign) {
    return http_response_code(401);
}

echo "request processed successfuly";

Payment notifications ​

In this section, we explain that each webhook sent by the system will contain a header with the Content-Type: application/json, indicating that the request body will contain information about a payment event and its status. The body of the webhook will have the same structure as the response to a request made to the /merchant/payment.create endpoint, but with the addition of a status field that can have one of two values: in_progress or success.

The in_progress status indicates that the payment is still being processed, and that additional updates may be forthcoming. The success status indicates that the payment has been successfully processed, by getting enough confirmations in the blockchain for secure processing. Developers can use these status values to automate their payment workflows and respond to payment events as necessary.

json
{
    "id": "2fedd721-794e-49c5-b92e-b3ecc6b1a6cc",
    "merchant_id": "88fd36a3-f3fd-48e6-b44a-5718dcf3dfa4",
    "description": "Telegram Premium",
    "asset": "usdt",
    "asset_amount": "1",
    "asset_amount_without_fee": "0.995",
    "asset_amount_decimals": "1000000",
    "asset_amount_decimals_without_fee": "995000",
    "fee": "5000",
    "status": "pending",
    "evm_withdraw_address": "0x1dd9bd5e214dbfeda80e56a995f15c31c48fbea0",
    "tron_withdraw_address": "TSvT6Bg3siokv3dbdtt9o4oM1CTXmymGn1",
    "selected_blockchain": null,
    "expires_at": "2023-04-27T11:49:41.84087Z",
    "created_at": "2023-04-27T10:49:41.840911Z"
}

WARNING

The merchant server must save the id and not process the webhook (just response with 200 status code) if it contains an id that has already been processed by the merchant system.