Newbie? Grab some coffee, this'll take about an hour. Pro? You know the drill - 5 minutes
HUBHUD is a robust PIX payment API designed to simplify payment processing for businesses of all sizes. Our API provides a seamless integration for processing instant payments through Brazil's PIX system.
This documentation is intended for developers who want to integrate PIX payments into their applications.
Getting Started
To begin using the HUBHUD API, you'll need to:
- Contact our sales team to obtain your API credentials
- Set up your development environment
- Follow the authentication process described below
Postman Collection
Download our Postman collection to quickly test the API endpoints:
Download Postman CollectionAuthentication
The HUBHUD API supports two authentication methods depending on the endpoint version.
Basic Authentication (v2 endpoints)
All /api/v2/* endpoints use HTTP Basic Authentication with your API Client credentials.
Encode your api_client_id:api_client_secret in Base64 and include it in the Authorization header:
{
"Content-Type": "application/json",
"Authorization": "Basic BASE64(api_client_id:api_client_secret)"
}
const credentials = btoa('YOUR_CLIENT_ID:YOUR_SECRET_ID');
// Use: Authorization: Basic ${credentials}
JWT Token (v1 / legacy endpoints)
Legacy endpoints, such as /api/withdrawals and /api/create-payment, use JWT Bearer Tokens.
Request Body (JWT)
| Field | Type | Description | Required |
|---|---|---|---|
email |
String | Your account's registered email. | Yes |
password |
String | Your account password. | Yes |
Response (JWT)
{
"success": true,
"message": "Token generated successfully",
"token": "YOUR_JWT_TOKEN",
"token_type": "Bearer",
"expires_at": "2025-07-01 00:21:56"
}
Create Pay-in (v2) NEW
Creates a new asynchronous pay-in request. This is the new, recommended method for creating deposits.
Asynchronous Flow
This endpoint is asynchronous. A successful request will return an HTTP 202 Accepted status, indicating the request was received and is being processed. The final payment details (including the QR Code) will be delivered to your registered deposit webhook.
Request Headers
This endpoint requires Basic Authentication. See the Authentication section.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
externalId |
String | A unique identifier for the transaction, generated by you. | Yes |
amount |
Number | The deposit amount. E.g., 5 for R$ 5,00. |
Yes |
document |
String | The payer's document number (CPF/CNPJ). | Yes |
name |
String | The full name of the payer. | Yes |
identification |
String | Additional identification field. | No |
expire |
Number | Expiration time for the payment in seconds. | No |
description |
String | A brief description of the payment. | Yes |
Success Response (202 Accepted)
{
"success": true,
"message": "Pay-in request received and is being processed.",
"payment_id": "62e2e461ac282e33d85",
"status": "pending"
}
Code Examples
curl -X POST 'https://api.hubhud.io/api/v2/create-payment' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-d '{
"externalId": "62e2e461ac282e33d85",
"amount": 5,
"document": "25689754895",
"name": "Hubhud",
"expire": 3600,
"description": "description"
}'
const b64Auth = btoa('YOUR_CLIENT_ID:YOUR_SECRET_ID');
const response = await fetch('https://api.hubhud.io/api/v2/create-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Basic ${b64Auth}`
},
body: JSON.stringify({
externalId: '62e2e461ac282e33d85',
amount: 5,
document: '25689754895',
name: 'Hubhud',
expire: 3600,
description: 'description'
})
});
const data = await response.json();
$client = new \GuzzleHttp\Client();
$response = $client->post('https://api.hubhud.io/api/v2/create-payment', [
'headers' => [
'Content-Type' => 'application/json'
],
'auth' => [
'YOUR_CLIENT_ID',
'YOUR_SECRET_ID'
],
'json' => [
'externalId' => '62e2e461ac282e33d85',
'amount' => 5,
'document' => '25689754895',
'name' => 'Hubhud',
'expire' => 3600,
'description' => 'description'
]
]);
$data = json_decode($response->getBody(), true);
import requests
headers = {
'Content-Type': 'application/json',
}
data = {
'externalId': '62e2e461ac282e33d85',
'amount': 5,
'document': '25689754895',
'name': 'Hubhud',
'expire': 3600,
'description': 'description'
}
response = requests.post(
'https://api.hubhud.io/api/v2/create-payment',
headers=headers,
json=data,
auth=('YOUR_CLIENT_ID', 'YOUR_SECRET_ID')
)
result = response.json()
Create Payment (v1)
Legacy endpoint — uses JWT Bearer Token authentication. For new integrations, prefer Create Pay-in (v2) or Create Payment (v2).
Creates a new PIX payment synchronously. This endpoint uses legacy JWT Auth.
Request Headers
{
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
}
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
externalId |
String | A unique identifier for the transaction. | Yes |
amount |
Number | The deposit amount. | Yes |
document |
String | The payer's document number (CPF/CNPJ). | Yes |
name |
String | The full name of the payer. | Yes |
identification |
String | Additional identification field. | No |
expire |
Number | Expiration time in seconds. | Yes |
Success Response
{
"success": true,
"message": "Payment processed successfully",
"data": {
"pix": "00020126850014br.gov.bcb.pix2563pix.voluti.com.br/...",
"uuid": "943c8da5-f771-4a10-b5a1-f4678d65ca4c",
"externalId": "012553856872857548828558",
"amount": "12.00",
"createdAt": "2025-04-25T23:05:42.180Z",
"expire": 3600
}
}
Withdrawals (v1)
Legacy endpoint — uses JWT Bearer Token authentication. For new integrations, prefer Withdrawals (v2).
Create a new withdrawal request to transfer funds to a PIX key. (This endpoint uses legacy JWT Auth).
Request Headers
{
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
}
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
externalId |
String | A unique identifier for the transaction. | Yes |
pixKey |
String | The destination PIX key. | Yes |
pixKeyType |
String | Enum: CPF, CNPJ, EMAIL, PHONE, EVP. |
Yes |
documentNumber |
String | The recipient's document number (CPF/CNPJ). | Yes |
name |
String | The full name of the recipient. | Yes |
amount |
Number | The withdrawal amount. | Yes |
Success Response
{
"success": true,
"message": "Withdrawal request created successfully",
"data": {
"id": "943c8da5-f771-4a10-b5a1-f4678d65ca4c",
"externalId": "45656286d2",
"amount": "20.00",
"status": "pending",
"createdAt": "2025-04-25T23:05:42.180Z"
}
}
(Bankslip) Boleto NEW
Endpoints for validating, paying, and querying boleto (bank slip) transactions. These endpoints use Basic
Authentication (api_client_id:api_client_secret).
Processes the payment of a bank slip (boleto). The barcode is read and validated internally — no prior call to a read endpoint is necessary. The payment is created asynchronously and processed in the background.
Authentication
Basic Auth — send an Authorization header with the base64 value of api_client_id:api_client_secret.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
barcode |
String | The barcode digits (numeric only, 44–60 characters). Also accepted as barCode.digitable (nested object). |
Yes |
payer_document |
String | CPF or CNPJ of the payer. Optional: auto-populated from barcode data via internal read if not provided. | No |
Success Response (200 OK)
{
"status": "ok",
"message": "Boleto payment created successfully",
"data": {
"id": "1234567890123",
"boleto": {
"createdAt": "2026-04-17T04:18:07.000000Z",
"updatedAt": "2026-04-17T04:25:15.000000Z",
"status": "PROCESSING",
"isPaid": "N",
"paidAt": null,
"barcode": "07790001161242351436606752570454914270000001000",
"type": 2,
"amounts": {
"original": "150.00",
"paid": "150.00",
"fee": "2.25",
"total": "152.25"
},
"payer": {
"document": "000.000.000-00"
},
"beneficiary": {
"name": "Example Beneficiary LTDA",
"document": "12.345.678/0001-90"
},
"fees": {
"service": {
"percentage": "1.50",
"amount": "2.25",
"currency": "BRL"
}
}
}
}
}
Error Response (400 Bad Request)
{
"status": "error",
"message": "Validation failed",
"errors": {
"barcode": ["The barcode must be between 44 and 60 characters."],
"tfa_code": ["The tfa_code field is required."],
"payer_document": ["The payer_document is not a valid document."]
}
}
Request Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/transactions/bankslip-out/payment' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-H 'Content-Type: application/json' \
-d '{
"barcode": "07790001161242351436606752570454914270000001000",
"amount": 150.00,
"tfa_code": "123456",
"payer_document": "00000000000191",
"beneficiary_name": "Example Beneficiary LTDA",
"beneficiary_document": "12345678000190",
"read_response": "{\"status\":\"ok\",\"data\":{\"value\":150.00,\"digitable\":\"07790001161242351436606752570454914270000001000\",\"type\":2,...}}"
}'
Important Notes
- Internal Read: The endpoint automatically reads and validates the barcode internally. There is no need to call a separate read endpoint before calling pay.
- 2FA via API: 2FA verification is always bypassed for this API endpoint, regardless of account settings. The
tfa_codefield is accepted but not validated. - Balance Check: The system verifies that the account has sufficient available balance to cover both the boleto amount and service fees before processing.
- Duplicate Prevention: The same barcode cannot be processed twice unless the previous attempt has a
failedorcancelledstatus. - Asynchronous Processing: The payment is queued for background processing. The response indicates the payment has been created with status
PROCESSING, but settlement may take time. - Already Paid: If the barcode was already paid by the same account, the endpoint returns
status: okwith the existing payment data (idempotent).
Returns the current status of a boleto payment. The lookup is scoped to the authenticated account and its allowed sub-accounts, so transactions from another account are never returned.
Authentication
Basic Auth - send an Authorization header with the base64 value of api_client_id:api_client_secret.
Smart Lookup
You can send any one of the fields below. The API searches in boleto history and payments using the provided identifier.
| Field | Type | Description | Required |
|---|---|---|---|
id |
String | Can be the boleto history ID (request_bankslips_out.id) or a payment ID (payments.id). |
One of these fields is required |
transaction_id |
String | Transaction identifier. It can match a boleto history ID, payments.external_payment_id, or payments.provider_transaction_id. |
One of these fields is required |
barcode |
String | Boleto barcode or digitable line. Punctuation is accepted and will be normalized before lookup. | One of these fields is required |
Request Examples
{
"id": "1717012345678123"
}
{
"id": "12345"
}
{
"transaction_id": "1717012345678123"
}
{
"barcode": "34191.79001 01043.510047 91020.150008 2 91070026000"
}
Request Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/transactions/bankslip-out/status' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-H 'Content-Type: application/json' \
-d '{
"barcode": "34191.79001 01043.510047 91020.150008 2 91070026000"
}'
Response - Pending
{
"status": "ok",
"data": {
"transactionId": "1717012345678123",
"status": "PENDING",
"payerName": "Cliente Exemplo",
"payerCpf": "12345678900"
}
}
Response - Paid
{
"status": "ok",
"data": {
"transactionId": "1717012345678123",
"status": "PAID",
"payerName": "Cliente Exemplo",
"payerCpf": "12345678900",
"paymentDate": "2026-05-29T18:42:10.000000Z",
"authentication": "ABC123456789XYZ"
}
}
Error Responses
{
"status": "error",
"message": "Transaction not found or access denied."
}
{
"status": "error",
"message": "Bankslip transaction not found for this payment."
}
Create Payment (v2) NEW
Creates a new PIX payment synchronously using Basic Authentication. This is the v2 version of Create Payment, updated to use API Client credentials (no JWT required).
Request Headers
Requires Basic Authentication (api_client_id:api_client_secret in Base64). See the Authentication section.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
externalId |
String | A unique identifier for the transaction, generated by you. | Yes |
amount |
Number | The deposit amount. E.g., 5 for R$ 5,00. |
Yes |
document |
String | The payer's document number (CPF/CNPJ). | Yes |
name |
String | The full name of the payer. | Yes |
identification |
String | Additional identification field. | No |
expire |
Number | Expiration time for the QR Code in seconds. | No |
description |
String | A brief description of the payment. | Yes |
Success Response (200 OK)
{
"success": true,
"message": "Payment processed successfully",
"data": {
"pix": "00020126850014br.gov.bcb.pix2563pix.example.com.br/...",
"uuid": "943c8da5-f771-4a10-b5a1-f4678d65ca4c",
"externalId": "012553856872857548828558",
"amount": "12.00",
"createdAt": "2025-04-25T23:05:42.180Z",
"expire": 3600
}
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/create-payment' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-d '{
"externalId": "012553856872857548828558",
"amount": 12,
"document": "25689754895",
"name": "Fulano de Tal",
"expire": 3600
}'
Transaction Status (v2) NEW
Returns the locally recorded status of a specific IN (pay-in) or OUT (pay-out) transaction. The lookup accepts either external_payment_id or provider_transaction_id.
Authentication
Requires HTTP Basic Authentication using api_client_id:api_client_secret. The authenticated account is determined exclusively from these credentials.
The lookup is always restricted by the authenticated account's internal ID and returns only client-visible transactions. Supplying an identifier that belongs to another account returns 404 Not Found.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
id | String | An external_payment_id or provider_transaction_id. | Yes |
Success Response — IN example (200 OK)
{
"success": true,
"externalId": "payin-order-123",
"providerTransactionId": "E00416968202511100200KYh0fOaevTK",
"status": "paid",
"type": "IN",
"amount": 100.00
}
Success Response — OUT example (200 OK)
{
"success": true,
"externalId": "payout-order-456",
"providerTransactionId": "E90400888202511100200ABC12345678",
"status": "processing",
"type": "OUT",
"amount": 75.50
}
Error Responses
{ "message": "Invalid API credentials." }
{ "message": "Transaction not found in this account." }
{
"message": "The id field is required.",
"errors": { "id": ["The id field is required."] }
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/transaction/status' \
-u 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{ "id": "YOUR_EXTERNAL_ID_OR_PROVIDER_ID" }'
Withdrawals (v2) NEW
Process a PIX withdrawal using Basic Authentication. This is the v2 version of Withdrawals, using API Client credentials instead of JWT tokens. 2FA is skipped for API requests.
Request Headers
Requires Basic Authentication (api_client_id:api_client_secret in Base64). See the Authentication section.
Note on Sender Name
The sender's name is automatically taken from the authenticated API client's account user. You do not need to include it in the request body.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
externalId |
String | A unique identifier for the transaction. | Yes |
pixKey |
String | The destination PIX key. | Yes |
pixKeyType |
String | Enum: CPF, CNPJ, EMAIL, PHONE, EVP. |
Yes |
documentNumber |
String | The recipient's document number (CPF/CNPJ). | Yes |
amount |
Number | The withdrawal amount in BRL. | Yes |
account_id |
Integer | Sub-account ID to withdraw from. Mandatory if the authenticated account has sub-accounts. | No |
Success Response (200 OK)
{
"success": true,
"message": "Withdraw processed successfully",
"data": {
"id": "943c8da5-f771-4a10-b5a1-f4678d65ca4c",
"externalId": "wd_abc123",
"amount": "50.00",
"status": "pending",
"createdAt": "2025-04-25T23:05:42.180Z"
},
"payment_id": "wd_abc123"
}
Error Responses
{
"success": false,
"message": "Insufficient balance.",
"errors": null
}
{
"error": "Withdrawal via API is blocked for this account"
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/withdrawals' \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-d '{
"externalId": "wd_abc123",
"pixKey": "[email protected]",
"pixKeyType": "EMAIL",
"documentNumber": "12345678901",
"amount": 50.00
}'
Balance (v2) NEW
Returns the balance for the authenticated account using Basic Authentication. The API reads only the balance row for the account's current acquirer: balances.account_id = account.id and balances.acquirer_id = accounts.acquirer_id.
Request Headers
This endpoint requires Basic Authentication. See the Authentication section.
Request Body
None. The account is determined from the authenticated API Client credentials.
Success Response (200 OK)
{
"success": true,
"data": {
"available_balance": "1250.50",
"blocked_balance": "80.00"
}
}
Error Responses
{
"message": "API credentials missing."
}
{
"message": "Invalid API credentials."
}
{
"success": false,
"message": "Your account has been deactivated administratively.",
"error": "account_inactive"
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/v2/balance' \
-H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
-H 'Accept: application/json'
Legacy Endpoints (JWT Auth)
These endpoints use JWT Bearer Token authentication (obtained via POST /api/login). All require the header Authorization: Bearer YOUR_JWT_TOKEN.
Returns the balance for the authenticated account using JWT Bearer Token authentication. The API reads only the balance row for the account's current acquirer: balances.account_id = account.id and balances.acquirer_id = accounts.acquirer_id.
Request Body
None. The account is determined from the authenticated JWT token.
Success Response (200 OK)
{
"success": true,
"data": {
"available_balance": "1250.50",
"blocked_balance": "80.00"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
available_balance | Number/String | Available balance on the account's current acquirer. |
blocked_balance | Number/String | Amount currently blocked on the account's current acquirer. |
Error Responses
{
"message": "Unauthenticated."
}
{
"message": "Account not found."
}
{
"message": "Unauthorized access to this account."
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/balance' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Accept: application/json'
Returns the status of a specific transaction. Searches by external_payment_id or provider_transaction_id.
Request Body
| Field | Type | Description | Required |
|---|---|---|---|
id | String | The externalId or provider transaction ID to look up. | Yes |
Success Response (200 OK)
{
"success": true,
"externalId": "62e2e461ac282e33d85",
"providerTransactionId": "E00416968202511100200KYh0fOaevTK",
"status": "paid",
"type": "IN",
"amount": 5.00
}
Error Responses
{ "message": "Transaction not found in this account." }
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/transaction/status' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "id": "62e2e461ac282e33d85" }'
Returns a paginated list of transactions for the authenticated account. All filters are optional. Results are paginated 15 per page and ordered by newest first.
Request Body (all optional)
| Field | Type | Description | Required |
|---|---|---|---|
status | String | Filter by status. Enum: paid, processing, pending, refunded, canceled. | No |
type_transaction | String | Filter by type. Enum: IN (deposit), OUT (withdrawal). | No |
date_from | String (date) | Start date filter (e.g. 2025-01-01). Used with date_to. | No |
date_to | String (date) | End date filter. Must be equal to or after date_from. | No |
Success Response (200 OK)
Returns a Laravel paginated resource collection. Each item is a PaymentResource object.
curl -X POST 'https://api.hubhud.io/api/transactions' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"status": "paid",
"type_transaction": "IN",
"date_from": "2025-01-01",
"date_to": "2025-01-31"
}'
Returns the sum of amounts and fees for paid transactions within a given period. Defaults to type_transaction=IN if not specified.
Request Body (all optional)
| Field | Type | Description | Required |
|---|---|---|---|
date | String (date) | Filter for a single specific date. | No |
startDate | String (date) | Start of date range. Used with endDate. | No |
endDate | String (date) | End of date range. Must be equal to or after startDate. | No |
type_transaction | String | Enum: IN or OUT. Default: IN. | No |
Success Response (200 OK)
{
"total_amount": 1250.50,
"total_fee": 18.75,
"currency": "BRL"
}
Code Example (cURL)
curl -X POST 'https://api.hubhud.io/api/transactions/totals' \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"startDate": "2025-01-01",
"endDate": "2025-01-31",
"type_transaction": "IN"
}'
Health Check
Returns the operational status of the API and its database connection. No authentication required.
Success Response (200 OK)
{ "status": "healthy" }
Error Response (503 Service Unavailable)
{ "status": "unhealthy", "database": "unreachable" }
Code Example (cURL)
curl -X GET 'https://api.hubhud.io/api/health'
Webhooks
Webhooks allow you to receive real-time notifications about payment status changes. Configure your webhook endpoint to receive these notifications automatically.
Webhook Configuration
To set up webhooks, you need to:
- Configure the webhook URL in your HUBHUD dashboard
- Implement the webhook handler to process incoming notifications
Webhook Events
You will receive notifications for the following events. We use a type field in the JSON payload to distinguish them.
Deposit Events (Pay-in)
- PAYIN_CONFIRMED: Sent when a deposit (pay-in) is successfully received and confirmed.
Withdrawal Events (Payout)
- PAYOUT_CONFIRMED: Sent when a withdrawal (payout) is successfully completed.
- PAYOUT_CANCELED: Sent when a withdrawal (payout) fails or is canceled.
Webhook Payloads
Event: PAYIN_CONFIRMED (Deposit)
Sent when a deposit is successfully paid by the customer.
{
"type": "PAYIN_CONFIRMED",
"externalId": "1762740042198_cxweu8hxx",
"amount": "2.25",
"status": "paid",
"fee_applied": 0,
"endToEndId": null,
"processed_at": "2025-11-09T23:01:12-03:00",
"uuid": "42dbed01-b917-4dc1-a483-f8d91e180c0a",
"metadata": {
"authCode": "42dbed01-b917-4dc1-a483-f8d91e180c0a",
"amount": "2.25",
"paymentDateTime": "2025-11-10T02:01:08.422+00:00",
"pixKey": "",
"receiveName": "INTERMEDIACOES LTDA",
"receiverName": "INTERMEDIACOES LTDA",
"receiverBankName": "33053580",
"receiverDocument": "46655005000153",
"receiveAgency": "0000",
"receiveAccount": "0000",
"payerName": "FULANO DE TAL",
"payerAgency": "0000",
"payerAccount": "0000",
"payerDocument": "58694521459",
"createdAt": "2025-11-09T23:00:43-03:00",
"endToEnd": "E00416968202511100200KYh0fOaevTK"
}
}
Event: PAYOUT_CONFIRMED (Withdrawal)
Sent when a withdrawal request is successfully processed and the funds are sent.
{
"type": "PAYOUT_CONFIRMED",
"externalId": "123457778698758",
"amount": "1.00",
"status": "paid",
"fee_applied": "0.30",
"endToEndId": "E3305358020250721211fb18",
"processed_at": "2025-07-21T21:13:13+00:00",
"uuid": "943c8da5-f771-4a10-b5a1-f4678d65ca4c",
"metadata": {
"pixKey": "00000000000",
"pixKeyType": "CPF",
"receiverName": "FULANO DE TAL",
"receiverBankName": "Banco Exemplo S.A.",
"receiverDocument": "11122233345"
}
}
Event: PAYOUT_CANCELED (Withdrawal)
Sent when a withdrawal request fails or is canceled (e.g., insufficient funds, invalid key).
{
"type": "PAYOUT_CANCELED",
"externalId": "9876543210",
"amount": "150.75",
"status": "canceled",
"processed_at": "2025-11-09T23:10:00-03:00",
"uuid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
"reason_cancelled": "Invalid PIX Key.",
"metadata": []
}
Webhook Security
Signature Verification
Each webhook request includes a signature in the X-signature header. Verify this signature to ensure the request came from HUBHUD.
Example Verification (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Usage
const isValid = verifyWebhook(
request.body,
request.headers['x-signature'],
process.env.WEBHOOK_SECRET
);
Important Notes
QR Code Expiration
The generated QR code is valid for the time specified in the expire field (in seconds). After this period, the QR code becomes invalid and a new payment must be created.
Best Practices
- Always validate the response status and handle errors appropriately.
- Store the
externalIdfor future reference and idempotency. - Implement proper error handling and retry mechanisms.
- Secure your webhook endpoint by verifying the
X-Signatureheader. - Prefer v2 endpoints (Basic Auth) for new integrations — JWT endpoints are legacy.