Webhook Event Verification
Webhook events can optionally have their content integrity and authenticity verified, using a signature present in the headers.
The verification process occurs when the integrator is capable of reproducing the generation technique and obtaining the same signature as the one received in the message.
This procedure ensures that the original message content has not been corrupted and that the encryption key used in the operation is the only one known by both teams.
Below are the details on how to implement this verification.
Step 1: Encryption Key
Paybrokers shares an encryption key with each of its integrators, which is used to sign the sent messages.
For convenience, this key can be generated by the integrator in the administrative panel, via the Webhooks Preferences Menu
To access this menu you need to:
- Access the partners portal and click in
Administrative
:

- If the key does not exist or if you want to change the existing one, request the generation of a new key by clicking
[Create new]
button. Press[Save]
button to confirm, or leave the screen to keep the previous key.

In this example, the generated key is presented below and will be used later in this tutorial:
291633849ff2447c9e58987ce3acfdd54c40eeb2fd8aa9b33203a440d5edefad4294c661f3686e75a9b17e8126cc8992
The saved key will be crucial for validating the content of the messages your system receives from Paybrokers. If you create and save a new key, update it in your system .
If you find it dificult to generate the key, Paybrokers team can generate the key and provide it to you.
Step 2: Validating the Message Content
To explain the validation of a message, let's consider its structure, below:
Headers | Content |
---|---|
Content-Type | application/json |
X-Webhook-Signature | HMAC-SHA256 Sign=5D90499D59FB0D9FAD44A15112936CFCABA73A6EE666AAA63B60A0FC03F40EA5, Nonce=b7891a74-ca9a-4770-bedd-8fd8341b122b,TS=1684633816 |
Body | Content |
---|---|
{"id":"f6431a0f-970a-4be9-9c6d-f444f729adc3","transactionState":"Completed", "transactionDate":"2023-05-19T19:51:21.320Z","transactionAmount":"0.010000", "transactionType":"Credit","transactionPaymentType":"PIX","payer":{"name":"Johnny Boy","taxNumber":"09977799400"}} |
A signed message has a payload in conventional JSON format. However, a field called X-Webhook-Signature
is added to the message headers. This field provides both the signature itself (Sign
) and the data required to replicate it, such as Nonce
and TS
(Timestamp).
To replicate the signature, the integrator must follow steps bellow.
Build a string that combines the X-Webhook-Signature
fields: Nonce
, TS
, and the information from the body content, separated by a colon :
.
Using the example message presented before, we have:
From the Header:
Nonce = b7891a74-ca9a-4770-bedd-8fd8341b122b
TS = 1684633816
From the Body:
{"id":"f6431a0f-970a-4be9-9c6d-f444f729adc3","transactionState":"Completed","transactionDate":"2023-05-19T19:51:21.320Z","transactionAmount":"0.010000","transactionType":"Credit","transactionPaymentType":"PIX","payer":{"name":"Johnny Boy","taxNumber":"09977799400"}}
Thus, the concatenated string has the following format:
b7891a74-ca9a-4770-bedd-8fd8341b122b:1684633816:{"id":"f6431a0f-970a-4be9-9c6d-f444f729adc3","transactionState":"Completed","transactionDate":"2023-05-19T19:51:21.320Z","transactionAmount":"0.010000","transactionType":"Credit","transactionPaymentType":"PIX","payer":{"name":"Johnny Boy","taxNumber":"09977799400"}}
Apply an HMAC generation algorithm with the SHA256 hash generation algorithm with two parameters: the string described above, as message
; and the key generated in the administrative panel, as key
.
For this purpose, you can use for instance the crypto library, as in the Javascript example bellow:
const crypto = require('crypto');
const message = `b7891a74-ca9a-4770-bedd-8fd8341b122b:1684633816:{"id":"f6431a0f-970a-4be9-9c6d-f444f729adc3","transactionState":"Completed","transactionDate":"2023-05-19T19:51:21.320Z","transactionAmount":"0.010000","transactionType":"Credit","transactionPaymentType":"PIX","payer":{"name":"Johnny Boy","taxNumber":"09977799400"}}`;
const key = "bf8867f612a34346a57d4e1c5e98b1ecc53defe3cccc4b7b8ea72dfbcf74a349";
const algorithm = 'sha256';
const hmac = crypto.createHmac(algorithm, key);
hmac.update(message);
const signature = hmac.digest('hex');
console.log(signature.toUpperCase());
If everything is correct, the generated signature should be as follows:
5D90499D59FB0D9FAD44A15112936CFCABA73A6EE666AAA63B60A0FC03F40EA5
This signature should match the Sign
field in X-Webhook-Signature
, ensuring the integrity of the received message.
🔒 For Customers Using Cloud Proxies (e.g. CloudFront, Cloudflare, NGINX)
If your infrastructure is behind HTTP proxies, CDNs, or reverse proxies — such as Amazon CloudFront, Cloudflare, or NGINX — it’s important to understand how request source attribution behaves when receiving webhook calls from Paybrokers.
1. Understand the X-Forwarded-For
behavior
X-Forwarded-For
behaviorWhen a request traverses multiple proxy layers (e.g., CDN → Load Balancer → Application), each intermediary typically appends its own IP address to the X-Forwarded-For
header.
This header becomes an unordered comma-separated list of IPs, representing the full request chain:
X-Forwarded-For: <original-client-ip>, <edge-ip>, <internal-ip>
In the case of Paybrokers, our webhooks originate from a static public IP:
18.229.232.194
If your infrastructure uses intermediate proxies, this IP will appear within the X-Forwarded-For
chain — not necessarily as the first or last entry, not necessarily solely at that field too.
2. Do not rely solely on remote_addr
(for those using NGINX)
remote_addr
(for those using NGINX)The remote_addr
(or $remote_addr
) field — commonly used in logs or access controls — reflects only the immediate upstream sender of the request, such as:
- A CloudFront edge node
- A reverse proxy
- An ingress controller
- A load balancer
This is not a reliable field to identify the original source of webhook traffic, especially in proxied environments.
3. Recommended validation logic
To reliably identify Paybrokers webhook traffic, your system should:
- Inspect the entire
X-Forwarded-For
chain - Validate that the IP
18.229.232.194
is present anywhere in that list - Avoid relying on or filtering by
remote_addr
Examples
CloudFront
X-Forwarded-For: 18.229.232.194, 130.176.23.1, 70.132.42.10
Cloudflare
X-Forwarded-For: 18.229.232.194, 162.158.158.123
Critical Notes
- Cloud proxies like CloudFront use a large, shared pool of IPs (e.g., 3.172.x.x), dynamically assigned by AWS. These IPs are not unique or stable per customer.
- Do not attempt to validate based on CloudFront or Cloudflare IPs, as they are reused across AWS clients.
- Always rely on the
X-Forwarded-For
header for source validation, where the Paybrokers IP will always be present.
If you need help implementing proper validation or parsing of this header, feel free to contact our technical team.
Updated about 2 months ago