Before you begin, it is highly recommended to review the Callbacks start page, as it explains some of the terms used here as well, in case you haven't seen it yet.
What is a callback notification in the webhook context?
In the context of webhooks, a callback refers to the URL or endpoint that a server uses to send data or a notification to another application after a specific event occurs. The server makes an HTTP request to the callback URL (the "webhook") with the event information, enabling real-time communication between systems.
For example, after a payment is processed, a webhook callback might be triggered to notify the client application with details about the transaction. This allows the application to update its status or take further action based on the event without needing to constantly poll the server for updates.
The webhook context is just an example for the usage of this callback notifications, It's possible to integrate in many other ways.
Signature Verification
When you receive callbacks, you will notice we send the "signature" header. The signature is an asymmetric encrypted public key.
The purpose of this public key is to verify that the signature originates from our private key and ensure that the payload has not been tampered with. It also provides the benefit of non-repudiation, meaning that the sender cannot deny having signed the payload. Once you receive a payload from us, you must verify the private key using our public key.
If you want to learn more about signature verification we recommend reading this link.
The following code example demonstrates how to validate the signature of a given request body. First, both the signature from the header and the request body should be retrieved as strings. Using the RS256 signature validation algorithm, the signature, body, and public key (all as strings) are verified. If the signature is missing or invalid, the request should be rejected and unauthorized from proceeding.
using System.Security.Cryptography;
using System.Text;
var publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnch8FX/GfxzLd1Yh/gGEdUMjiwCQu5Git9k9HtIXPMfvRHSznk1oCFQrYM2eOKqrU6TSBT2RkBpU4BGD53tghZ6Kmy+SDQ9QWPMCaBIxUnDPn7r0/5seOiAFOppTrVqQu9A81NovPfcvKRF8PIuvSEEQT/6J2TaI8S40fcgOo1eqIbCaPSfarvU3zdHDjgvvi+LI+SC5JbpkeykZapopVphHFvqUCQAfAmlumHvO7QL6TxnvPTewXtTx3oZWOPFq8ZZVAthZK5kMLGEAyACcY6KpvgVfG7DC9Kc94QN2uZpgczJkQ0xTtIOiMjjE5kmdcozWPAu4tQ1aSrfFLDF/QIDAQAB";
var webhookPayload = "{\"accountId\":\"341\",\"amount\":{\"amount\":41.71,\"currency\":\"BRL\"},\"amountNet\":{\"amount\":41.71,\"currency\":\"BRL\"},\"transactionDescription\":{\"description\":\"CREDIT TRANSACTION CREATED BY SCRIPT\",\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"taxIdCountry\":76},\"date\":\"2022-09-23\",\"dateDetailed\":\"2022-09-23T19:10:00.1573834+00:00\",\"type\":\"Credit\",\"transactionId\":\"4aa90ec4-4ede-4f8c-qwed-88baee3632d4\",\"counterpart\":{\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"bankAccount\":\"2342-4\",\"bankBranch\":\"2342\",\"taxIdCountry\":76},\"blockchain\":\"None\"}";
var signature = "QdZ+Oj7QLWf0LGhUadNktcNGHGKs1VpQQqIsY2W96lGt/b3HKIjBfntmOUAsV1zSXFPzGvbFyaHbbH+Cx3aw0S/xhKe2ZOQeRDmHtmWXM7Xih0qmfOnQmYUx/GfkxSJwh2b5yHVb91y6rq8kXUhPaHdZz+JX+YcD4WFFaY5TYDdwaiFfSeyaaIObgjq8PPiIELd+oyPDGacdRtnhbcKSmZ7G2TZ/ViSSh4VSWQxOElnDz5k/OXlTHewSWJooZua1b0CAvdBaB8n+KSUwdk8bw2eyr404KvXQOwOJfFmrQEUfpLEb1IWbSYnf6aIEvO3RwzhNMzDzsaLWrCGZjSaSLQ==";
var rsa = RSA.Create();
var publicKeyAsBytes = Convert.FromBase64String(publicKey);
rsa.ImportSubjectPublicKeyInfo(publicKeyAsBytes, out _);
var signatureAsBytes = Convert.FromBase64String(signature);
var dataAsBytes = Encoding.UTF8.GetBytes(webhookPayload);
var verify = rsa.VerifyData(dataAsBytes, signatureAsBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Console.WriteLine("Is valid? " + verify);
<?php
namespace App;
use \phpseclib\Crypt\RSA;
//sample transaction callback
$publicKey = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnch8FX/GfxzLd1Yh/gGEdUMjiwCQu5Git9k9HtIXPMfvRHSznk1oCFQrYM2eOKqrU6TSBT2RkBpU4BGD53tghZ6Kmy+SDQ9QWPMCaBIxUnDPn7r0/5seOiAFOppTrVqQu9A81NovPfcvKRF8PIuvSEEQT/6J2TaI8S40fcgOo1eqIbCaPSfarvU3zdHDjgvvi+LI+SC5JbpkeykZapopVphHFvqUCQAfAmlumHvO7QL6TxnvPTewXtTx3oZWOPFq8ZZVAthZK5kMLGEAyACcY6KpvgVfG7DC9Kc94QN2uZpgczJkQ0xTtIOiMjjE5kmdcozWPAu4tQ1aSrfFLDF/QIDAQAB
-----END PUBLIC KEY-----';
$callbackPayload = '{"accountId":"341","amount":{"amount":41.71,"currency":"BRL"},"amountNet":{"amount":41.71,"currency":"BRL"},"transactionDescription":{"description":"CREDIT TRANSACTION CREATED BY SCRIPT","name":"Marcos da Silva","taxId":"44115144432","taxIdCountry":76},"date":"2022-09-23","dateDetailed":"2022-09-23T19:10:00.1573834+00:00","type":"Credit","transactionId":"4aa90ec4-4ede-4f8c-qwed-88baee3632d4","counterpart":{"name":"Marcos da Silva","taxId":"44115144432","bankAccount":"2342-4","bankBranch":"2342","taxIdCountry":76},"blockchain":"None"}';
$signature = 'QdZ+Oj7QLWf0LGhUadNktcNGHGKs1VpQQqIsY2W96lGt/b3HKIjBfntmOUAsV1zSXFPzGvbFyaHbbH+Cx3aw0S/xhKe2ZOQeRDmHtmWXM7Xih0qmfOnQmYUx/GfkxSJwh2b5yHVb91y6rq8kXUhPaHdZz+JX+YcD4WFFaY5TYDdwaiFfSeyaaIObgjq8PPiIELd+oyPDGacdRtnhbcKSmZ7G2TZ/ViSSh4VSWQxOElnDz5k/OXlTHewSWJooZua1b0CAvdBaB8n+KSUwdk8bw2eyr404KvXQOwOJfFmrQEUfpLEb1IWbSYnf6aIEvO3RwzhNMzDzsaLWrCGZjSaSLQ==';
$rsa = new RSA();
$rsa ->setHash('sha256');
$rsa ->setPublicKeyFormat('CRYPT_RSA_PUBLIC_FORMAT_PKCS1');
$loadKeyResponse = $rsa ->loadKey($publicKey);
if ($loadKeyResponse == 1) {
echo "good key\n";
} elseif ($loadKeyResponse == 0) {
echo "bad key\n";
} else {
echo "ugly, error checking key\n";
}
$verification = $rsa ->_rsassa_pkcs1_v1_5_verify($callbackPayload,base64_decode($signature));
if ($verification == 1) {
echo "good\n";
} elseif ($verification == 0) {
echo "bad\n";
} else {
echo "ugly, error checking signature\n";
}
?>
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64decode
public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnch8FX/GfxzLd1Yh/gGEdUMjiwCQu5Git9k9HtIXPMfvRHSznk1oCFQrYM2eOKqrU6TSBT2RkBpU4BGD53tghZ6Kmy+SDQ9QWPMCaBIxUnDPn7r0/5seOiAFOppTrVqQu9A81NovPfcvKRF8PIuvSEEQT/6J2TaI8S40fcgOo1eqIbCaPSfarvU3zdHDjgvvi+LI+SC5JbpkeykZapopVphHFvqUCQAfAmlumHvO7QL6TxnvPTewXtTx3oZWOPFq8ZZVAthZK5kMLGEAyACcY6KpvgVfG7DC9Kc94QN2uZpgczJkQ0xTtIOiMjjE5kmdcozWPAu4tQ1aSrfFLDF/QIDAQAB"
webhook_payload = b"{\"accountId\":\"341\",\"amount\":{\"amount\":41.71,\"currency\":\"BRL\"},\"amountNet\":{\"amount\":41.71,\"currency\":\"BRL\"},\"transactionDescription\":{\"description\":\"CREDIT TRANSACTION CREATED BY SCRIPT\",\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"taxIdCountry\":76},\"date\":\"2022-09-23\",\"dateDetailed\":\"2022-09-23T19:10:00.1573834+00:00\",\"type\":\"Credit\",\"transactionId\":\"4aa90ec4-4ede-4f8c-qwed-88baee3632d4\",\"counterpart\":{\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"bankAccount\":\"2342-4\",\"bankBranch\":\"2342\",\"taxIdCountry\":76},\"blockchain\":\"None\"}"
signature = "QdZ+Oj7QLWf0LGhUadNktcNGHGKs1VpQQqIsY2W96lGt/b3HKIjBfntmOUAsV1zSXFPzGvbFyaHbbH+Cx3aw0S/xhKe2ZOQeRDmHtmWXM7Xih0qmfOnQmYUx/GfkxSJwh2b5yHVb91y6rq8kXUhPaHdZz+JX+YcD4WFFaY5TYDdwaiFfSeyaaIObgjq8PPiIELd+oyPDGacdRtnhbcKSmZ7G2TZ/ViSSh4VSWQxOElnDz5k/OXlTHewSWJooZua1b0CAvdBaB8n+KSUwdk8bw2eyr404KvXQOwOJfFmrQEUfpLEb1IWbSYnf6aIEvO3RwzhNMzDzsaLWrCGZjSaSLQ=="
b64key = b64decode(public_key)
key = RSA.importKey(b64key)
cipher = PKCS1_v1_5.new(key)
md5_digest = SHA256.new(webhook_payload)
verify = cipher.verify(md5_digest, b64decode(signature))
print("Is valid? " + str(verify))
var crypto = require('crypto');
var ALGORITHM = "SHA256";
var SIGNATURE_FORMAT = "base64";
var publicKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnch8FX/GfxzLd1Yh/gGEdUMjiwCQu5Git9k9HtIXPMfvRHSznk1oCFQrYM2eOKqrU6TSBT2RkBpU4BGD53tghZ6Kmy+SDQ9QWPMCaBIxUnDPn7r0/5seOiAFOppTrVqQu9A81NovPfcvKRF8PIuvSEEQT/6J2TaI8S40fcgOo1eqIbCaPSfarvU3zdHDjgvvi+LI+SC5JbpkeykZapopVphHFvqUCQAfAmlumHvO7QL6TxnvPTewXtTx3oZWOPFq8ZZVAthZK5kMLGEAyACcY6KpvgVfG7DC9Kc94QN2uZpgczJkQ0xTtIOiMjjE5kmdcozWPAu4tQ1aSrfFLDF/QIDAQAB\n-----END PUBLIC KEY-----";
var webhookPayload = "{\"accountId\":\"341\",\"amount\":{\"amount\":41.71,\"currency\":\"BRL\"},\"amountNet\":{\"amount\":41.71,\"currency\":\"BRL\"},\"transactionDescription\":{\"description\":\"CREDIT TRANSACTION CREATED BY SCRIPT\",\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"taxIdCountry\":76},\"date\":\"2022-09-23\",\"dateDetailed\":\"2022-09-23T19:10:00.1573834+00:00\",\"type\":\"Credit\",\"transactionId\":\"4aa90ec4-4ede-4f8c-qwed-88baee3632d4\",\"counterpart\":{\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"bankAccount\":\"2342-4\",\"bankBranch\":\"2342\",\"taxIdCountry\":76},\"blockchain\":\"None\"}";
var signature = "QdZ+Oj7QLWf0LGhUadNktcNGHGKs1VpQQqIsY2W96lGt/b3HKIjBfntmOUAsV1zSXFPzGvbFyaHbbH+Cx3aw0S/xhKe2ZOQeRDmHtmWXM7Xih0qmfOnQmYUx/GfkxSJwh2b5yHVb91y6rq8kXUhPaHdZz+JX+YcD4WFFaY5TYDdwaiFfSeyaaIObgjq8PPiIELd+oyPDGacdRtnhbcKSmZ7G2TZ/ViSSh4VSWQxOElnDz5k/OXlTHewSWJooZua1b0CAvdBaB8n+KSUwdk8bw2eyr404KvXQOwOJfFmrQEUfpLEb1IWbSYnf6aIEvO3RwzhNMzDzsaLWrCGZjSaSLQ==";
var verify = crypto.createVerify(ALGORITHM);
verify.update(webhookPayload);
var verification = verify.verify(publicKey, signature, SIGNATURE_FORMAT);
console.log('Is valid? ' + verification);
package com.payme.demo;
import com.alibaba.fastjson.JSONObject;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Verification {
public static void main(String[] args) throws Exception {
String content = "{\"accountId\":\"341\",\"amount\":{\"amount\":41.71,\"currency\":\"BRL\"},\"amountNet\":{\"amount\":41.71,\"currency\":\"BRL\"},\"transactionDescription\":{\"description\":\"CREDIT TRANSACTION CREATED BY SCRIPT\",\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"taxIdCountry\":76},\"date\":\"2022-09-23\",\"dateDetailed\":\"2022-09-23T19:10:00.1573834+00:00\",\"type\":\"Credit\",\"transactionId\":\"4aa90ec4-4ede-4f8c-qwed-88baee3632d4\",\"counterpart\":{\"name\":\"Marcos da Silva\",\"taxId\":\"44115144432\",\"bankAccount\":\"2342-4\",\"bankBranch\":\"2342\",\"taxIdCountry\":76},\"blockchain\":\"None\"}";
JSONObject parse = JSONObject.parseObject(content);
System.out.println(parse.toJSONString());
String sign = "QdZ+Oj7QLWf0LGhUadNktcNGHGKs1VpQQqIsY2W96lGt/b3HKIjBfntmOUAsV1zSXFPzGvbFyaHbbH+Cx3aw0S/xhKe2ZOQeRDmHtmWXM7Xih0qmfOnQmYUx/GfkxSJwh2b5yHVb91y6rq8kXUhPaHdZz+JX+YcD4WFFaY5TYDdwaiFfSeyaaIObgjq8PPiIELd+oyPDGacdRtnhbcKSmZ7G2TZ/ViSSh4VSWQxOElnDz5k/OXlTHewSWJooZua1b0CAvdBaB8n+KSUwdk8bw2eyr404KvXQOwOJfFmrQEUfpLEb1IWbSYnf6aIEvO3RwzhNMzDzsaLWrCGZjSaSLQ==";
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnch8FX/GfxzLd1Yh/gGEdUMjiwCQu5Git9k9HtIXPMfvRHSznk1oCFQrYM2eOKqrU6TSBT2RkBpU4BGD53tghZ6Kmy+SDQ9QWPMCaBIxUnDPn7r0/5seOiAFOppTrVqQu9A81NovPfcvKRF8PIuvSEEQT/6J2TaI8S40fcgOo1eqIbCaPSfarvU3zdHDjgvvi+LI+SC5JbpkeykZapopVphHFvqUCQAfAmlumHvO7QL6TxnvPTewXtTx3oZWOPFq8ZZVAthZK5kMLGEAyACcY6KpvgVfG7DC9Kc94QN2uZpgczJkQ0xTtIOiMjjE5kmdcozWPAu4tQ1aSrfFLDF/QIDAQAB";
String charset = "UTF-8";
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.getDecoder().decode(publicKey);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature
.getInstance("SHA256WithRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes(charset));
boolean result = signature.verify(Base64.getDecoder().decode(sign.getBytes()));
System.out.println(result);
}
}
Public Key
This is the Production 2048 size RSA key:
ProductionWebhookKey.pem
This is the Staging 2048 size RSA key:
StagingWebhookKey.pem
IP Allowlisting (or WhiteList)
Another option for managing the signature verification process is to create a whitelist of our IP addresses. Below, we provide the IP for the staging environment. If you require the production IP address, we recommend submitting a support ticket.
20.201.84.244
20.226.242.146
While using a whitelist is an option, we strongly recommend verifying the signature as well, even if the IP addresses are whitelisted.
Retry Policy
Each webhook sent by Transfero has a 10-second timeout. In the case of a failed notification, the system will retry up to 12 times using an exponential backoff strategy. After the first failure, a retry is attempted immediately, followed by subsequent retries at intervals of 2, 4, 8, ..., up to 4096 minutes (approximately 68 hours).
About the subscriptions
On the following pages, you will be able to see the types of subscriptions available. Please note that you can only have up to two subscriptions for each callback type.