ConnectPSP sends asynchronous HTTP POST events to your webhookUrl when transactions change status.
Two events — CASHIN_REQUESTED and CASHOUT_REQUESTED — are only delivered to the additional webhook endpoints you register in the ConnectPSP panel. See Additional Webhook Endpoints.
All events share the same envelope:
{
"eventType": "CASHIN_PAID",
"eventAt": "2026-03-10T14:22:18Z",
"data": { ... }
}| Field | Description |
|---|---|
eventType | Identifies the event (see list below) |
eventAt | ISO 8601 UTC timestamp of when the event was dispatched |
data | Transaction details — mirrors the equivalent GET endpoint response |
The data object of every Cash-In and Cash-Out event (except the request-time events below) is the same object returned by GET /cash-in/{id} or GET /cash-out/{id} at that moment: the same fields, the same status values, the same failure / reason codes and the same settlement block. If a field is in one, it is in the other.
settlement is the ConnectPSP-side bank and account of the transaction:
- Cash-In — present only on
PAID/REFUNDED, the account that received the money. Absent onCASHIN_EXPIREDand any other status. - Cash-Out — on
COMPLETED/REFUNDED, the account that settled the payment. OnCANCELLED(CASHOUT_FAILED), the account the Cash-Out would have been paid from, present only when a bank had already been assigned — no money left your account. Absent onPROCESSING/PENDING.
There is one documented difference, on refunded Cash-Ins only: the webhook keeps endToEndId as the original payment E2E and adds refundEndToEndId, while the GET endpoint returns the devolution E2E in endToEndId with the original in originalEndToEndId.
ConnectPSP will POST to your webhookUrl using:
- Method:
POST - Content-Type:
application/json - Headers sent:
X-Connect-Signature— HMAC-SHA256 hash of the request body using yourCryptoToken. Use this to verify the request is genuine.X-Event-Id— Unique UUID for each delivery attempt.
Important: the HMAC key is your
CryptoTokenexactly as displayed in the panel — an uppercase GUID (with hyphens). Using a lowercase version of the token will produce a different signature.
Your endpoint must respond with HTTP 200 OK. Respond as fast as possible (ideally under 5 seconds); requests time out after 15 seconds for Cash-In events and 60 seconds for Cash-Out events. No response body is required.
If your endpoint fails (timeout, 5xx, etc.), ConnectPSP will retry the delivery a limited number of times. Deliveries are at-least-once: design your handler to be idempotent (deduplicate by data.transactionId or data.internalCode).
const crypto = require('crypto');
function isValidWebhook(rawBody, signatureHeader, cryptoToken) {
const expected = crypto
.createHmac('sha256', cryptoToken)
.update(rawBody) // rawBody = the raw request body string, NOT parsed JSON
.digest('hex');
return expected === signatureHeader;
}
// Express example
app.post('/webhooks/connectpsp', (req, res) => {
const signature = req.headers['x-connect-signature'];
const rawBody = req.rawBody; // ensure you capture raw body before JSON parsing
if (!isValidWebhook(rawBody, signature, process.env.CRYPTO_TOKEN)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
// process event...
res.status(200).send();
});Besides the webhookUrl you send in each request, you can register additional endpoints in the ConnectPSP panel, under Integrations → Webhooks. For each endpoint you choose which event types it should receive. This is useful to fan the same events out to a second system (anti-fraud, BI, ledger) without changing your integration.
Additional endpoints receive the same envelope, the same headers and the same signature described above — the HMAC key is the same CryptoToken.
CASHIN_REQUESTED and CASHOUT_REQUESTED are fired the moment the transaction is created, before any banking outcome exists. They behave differently from every other event:
- They are sent only to additional endpoints that subscribe to them in the panel.
- They are never sent to the
webhookUrlpassed in the request body. Subscribing in the panel is the only way to receive them. - They are fire-and-forget: they never delay or fail the API response, and a
2xxAPI response does not guarantee the event was delivered. - They are fired only when the transaction was created successfully. A rejected request produces no event.
Use them for real-time monitoring and to reconcile intent — never as confirmation of payment. Money is only confirmed by CASHIN_PAID / CASHOUT_COMPLETED.
Fired right after the Cash-In is created and the QR Code (or OpenFinance link) is generated — no payment has happened yet. Only sent to additional endpoints registered in the panel (see Request-time events).
data.status is "AWAITING_PAYMENT" — the same status the POST /cash-in response and GET /cash-in/{id} report at this stage. Only the event name says REQUESTED.
{
"eventType": "CASHIN_REQUESTED",
"eventAt": "2026-03-10T11:20:00.412Z",
"data": {
"transactionId": "kk6g232xel65a0daee4dd13kk2912714964",
"internalCode": "US7B1JQ",
"externalReference": "order_abc123",
"status": "AWAITING_PAYMENT",
"amount": 150.50,
"requestedAt": "2026-03-10T11:20:00-03:00",
"payer": {
"name": "João Silva",
"document": "12345678909"
}
}
}| Field | Description |
|---|---|
transactionId | ConnectPSP transaction identifier (same value returned by POST /cash-in) |
internalCode | Short code used on the GET /cash-in/{id} endpoint |
externalReference | Your own reference, echoed back. Omitted if you did not send one |
status | Always AWAITING_PAYMENT |
amount | Requested amount in BRL, with decimals |
requestedAt | Creation timestamp, ISO 8601 with -03:00 offset |
payer.name / payer.document | Payer data exactly as you sent it — not yet validated against the paying account |
The payload has no
pixData,endToEndIdor bank data: none of that exists yet. Take the copy-and-paste string from thePOST /cash-inresponse.
Fired when a PIX payment is confirmed by the banking network.
{
"eventType": "CASHIN_PAID",
"eventAt": "2026-03-10T11:22:18Z",
"data": {
"transactionId": "kk6g232xel65a0daee4dd13kk2912714964",
"internalCode": "US7B1JQ",
"externalReference": "order_abc123",
"endToEndId": "E00416968202603101827cemeFscF6AG",
"status": "PAID",
"amount": 150.50,
"payer": {
"name": "João Silva",
"document": "12345678909",
"bankData": {
"ispb": "00000000",
"bank": "Banco do Brasil S.A.",
"branch": "0001",
"account": "123456"
}
},
"settlement": {
"ispb": "12345678",
"bank": "SETTLEMENT PARTNER A",
"branch": "0001",
"account": "123456"
},
"requestedAt": "2026-03-10T11:20:00-03:00",
"paidAt": "2026-03-10T11:22:15-03:00"
}
}Fired after a paid Cash-In is refunded (via API, ConnectPSP panel, or a banking devolution). endToEndId keeps the original payment E2E; refundEndToEndId carries the devolution identifier.
reason tells you why the money went back to the payer — the same value GET /cash-in/{id} returns:
reason | Meaning |
|---|---|
FRAUD | Refunded because of fraud, including a MED (Special Return Mechanism) claim |
DUPLICATED_PAYMENT | You requested the refund with this reason |
CUSTOMER_REQUEST | Refund at the customer's request — the reason you sent, or a devolution the bank flagged as requested by the customer |
MERCHANT_ERROR | You requested the refund with this reason |
PAYER_NOT_ALLOWED | Refunded automatically because the payer is not allowed to pay you |
OTHER | Any other reason |
{
"eventType": "CASHIN_REFUNDED",
"eventAt": "2026-03-11T09:30:17Z",
"data": {
"transactionId": "kk6g232xel65a0daee4dd13kk2912714964",
"internalCode": "US7B1JQ",
"externalReference": "order_abc123",
"endToEndId": "E00416968202603101827cemeFscF6AG",
"refundEndToEndId": "D00416968202603101827cemeFscF6AG",
"status": "REFUNDED",
"reason": "CUSTOMER_REQUEST",
"amount": 150.50,
"payer": {
"name": "João Silva",
"document": "12345678909",
"bankData": {
"ispb": "00000000",
"bank": "Banco do Brasil S.A.",
"branch": "0001",
"account": "123456"
}
},
"settlement": {
"ispb": "12345678",
"bank": "SETTLEMENT PARTNER A",
"branch": "0001",
"account": "123456"
},
"requestedAt": "2026-03-10T11:20:00-03:00",
"paidAt": "2026-03-10T11:22:15-03:00",
"refundedAt": "2026-03-11T09:30:15-03:00"
}
}Fired when a Cash-In (QR Code) reaches its expiration (expirationInMinutes) without being paid.
Expect it 5 to 7 minutes after the expiration time: charges are expired in periodic batches, not at the exact second. Size any timeout on your side to that window.
{
"eventType": "CASHIN_EXPIRED",
"eventAt": "2026-03-11T12:26:00Z",
"data": {
"transactionId": "kk6g232xel65a0daee4dd13kk2912714964",
"internalCode": "US7B1JQ",
"externalReference": "order_abc123",
"status": "EXPIRED",
"amount": 150.50,
"requestedAt": "2026-03-10T11:20:00-03:00",
"expiredAt": "2026-03-11T09:20:00-03:00"
}
}expiredAt: the charge's expiration date and time (the end of the payment window). Same value on GET by id, on the list and on the CASHIN_EXPIRED webhook. CASHIN_EXPIRED is sent 5 to 7 minutes after the expiration; the envelope eventAt is the send time, not the expiration.
Fired when a Cash-In fails before settlement (rare — most failures surface as the QR simply not being payable). The data object follows the same shape as the other Cash-In events, with status: "FAILED".
A Cash-Out is processed asynchronously. POST /cash-out answers 202 Accepted before balance, limits, the PIX key and the receiving institution are checked, so an accepted Cash-Out can still be cancelled. Every outcome arrives here:
| Event | data.status | Money | Extra fields |
|---|---|---|---|
CASHOUT_COMPLETED | COMPLETED | Paid to the payee | completedAt, settlement |
CASHOUT_FAILED | CANCELLED | Never left your account | failure, cancelledAt, settlement (when a bank was assigned) |
CASHOUT_REFUNDED | REFUNDED | Paid, then came back to your account | reason, refundedAt, settlement |
The event names are stable. Note that CASHOUT_FAILED carries status: "CANCELLED" — there is no FAILED status on a Cash-Out.
Fired right after the Cash-Out is accepted for processing, before it is sent to the banking network. Only sent to additional endpoints registered in the panel (see Request-time events).
data.status is "PROCESSING" — the same status the POST /cash-out response and GET /cash-out/{id} report at this stage. Only the event name says REQUESTED.
{
"eventType": "CASHOUT_REQUESTED",
"eventAt": "2026-03-10T14:00:00.317Z",
"data": {
"transactionId": "dd30446e-6cc5-4664-bf3f-6b7f5e55a1a9",
"internalCode": "IVKPRMOCDY",
"externalReference": "withdraw_xyz789",
"status": "PROCESSING",
"amount": 500.00,
"requestedAt": "2026-03-10T14:00:00-03:00",
"payee": {
"name": "Maria Silva",
"document": "12345678909"
}
}
}| Field | Description |
|---|---|
transactionId | ConnectPSP transaction identifier (same value returned by POST /cash-out) |
internalCode | Short code used on the GET /cash-out/{id} endpoint |
externalReference | Your own reference, echoed back. Omitted if you did not send one |
status | Always PROCESSING |
amount | Requested amount in BRL, with decimals |
requestedAt | Creation timestamp, ISO 8601 with -03:00 offset |
payee.name / payee.document | Payee data exactly as you sent it — not yet resolved against the PIX key owner |
The payload has no
destination,bankDataorendToEndId. The destination is only resolved later, and arrives onCASHOUT_COMPLETED/CASHOUT_FAILED.
Fired when BACEN/SPI confirms the payment was successfully settled in the payee's account.
{
"eventType": "CASHOUT_COMPLETED",
"eventAt": "2026-03-10T14:02:35Z",
"data": {
"transactionId": "dd30446e-6cc5-4664-bf3f-6b7f5e55a1a9",
"internalCode": "IVKPRMOCDY",
"externalReference": "withdraw_xyz789",
"endToEndId": "E00416968202603101827cemeFscF6AG",
"status": "COMPLETED",
"amount": 500.00,
"payee": {
"name": "Maria Silva",
"document": "12345678909",
"destination": {
"type": "PIX",
"pixKeyType": "CPF",
"pixKey": "12345678909"
},
"bankData": {
"ispb": "00000000",
"bank": "Banco do Brasil S.A.",
"branch": "0001",
"account": "123456"
}
},
"settlement": {
"ispb": "87654321",
"bank": "SETTLEMENT PARTNER B",
"branch": "0001",
"account": "654987"
},
"requestedAt": "2026-03-10T14:00:00-03:00",
"completedAt": "2026-03-10T14:02:30-03:00"
}
}Fired when a Cash-Out is cancelled and the money never left your account — for example insufficient balance, a limit reached, a PIX key that does not exist, or a rejection by the receiving institution. The amount stays available in your transactional balance; no action is needed to recover it.
data.status is always CANCELLED, with failure and cancelledAt. settlement, when present, is the ConnectPSP account the Cash-Out would have been paid from — nothing was debited from it.
{
"eventType": "CASHOUT_FAILED",
"eventAt": "2026-03-10T14:00:20Z",
"data": {
"transactionId": "dd30446e-6cc5-4664-bf3f-6b7f5e55a1a9",
"internalCode": "IVKPRMOCDY",
"externalReference": "withdraw_xyz789",
"status": "CANCELLED",
"amount": 500.00,
"failure": {
"code": "PIX_KEY_NOT_FOUND",
"message": "The provided PIX key did not resolve to a valid account."
},
"payee": {
"name": "Maria Silva",
"document": "12345678909",
"destination": {
"type": "PIX",
"pixKeyType": "CPF",
"pixKey": "12345678909"
}
},
"settlement": {
"ispb": "87654321",
"bank": "SETTLEMENT PARTNER B",
"branch": "0001",
"account": "654987"
},
"requestedAt": "2026-03-10T14:00:00-03:00",
"cancelledAt": "2026-03-10T14:00:15-03:00"
}
}failure.message is fixed per code. Key your handling on code; new codes may be added over time, so treat a code you do not know as PROCESSING_ERROR.
failure.code | failure.message | When it happens | Retry? |
|---|---|---|---|
PROVIDER_UNAVAILABLE | The withdrawal could not be completed due to a temporary instability. The amount remains available in your account. | Temporary instability at ConnectPSP or at the settlement partner | Yes |
PIX_KEY_NOT_FOUND | The provided PIX key did not resolve to a valid account. | The PIX key is not registered in the PIX directory (DICT) | No — ask the payee for another key |
PIX_KEY_INVALID | The provided PIX key has an invalid format. | The key does not match the format of its pixKeyType | No — fix the key |
PAYEE_DOCUMENT_MISMATCH | The PIX key or account does not belong to the informed payee document. | The owner of the key or account is not payee.document | No |
PAYEE_ACCOUNT_INVALID | The destination account data is invalid. | Bank account data (ISPB, branch, account, type) does not identify an account | No — fix the account data |
PAYEE_ACCOUNT_UNAVAILABLE | The destination account is blocked, closed or cannot receive this transfer. | The destination account is blocked, closed or does not accept this transfer | No |
PAYEE_BANK_UNAVAILABLE | The receiving institution did not respond in time. The amount remains available in your account. | The payee's bank did not answer in time | Yes |
PAYEE_BANK_REJECTED | The receiving institution rejected the transfer. | The payee's bank refused the transfer | Only after checking with the payee |
FRAUD_SUSPECTED | The transfer was blocked by a security restriction on the payee. | A security or anti-fraud restriction on the payee | No |
INSUFFICIENT_FUNDS | Insufficient balance to complete the withdrawal. | Your transactional balance did not cover the amount | Yes, after funding the account (see POST /account/rebalance) |
AMOUNT_ABOVE_MAXIMUM | The amount exceeds the maximum allowed for this transfer. | Above the maximum per transfer | Only with a lower amount |
DAILY_LIMIT_EXCEEDED | The daily withdrawal limit has been reached. | Your daily withdrawal limit was reached | Yes, on the next day |
PERIOD_LIMIT_EXCEEDED | The withdrawal limit for the current period has been reached. | The limit for the current period (daytime or nighttime) was reached | Yes, in the next period |
PAYEE_LIMIT_EXCEEDED | The withdrawal limit for this payee has been reached. | The limit per payee was reached | Yes, when the payee's limit resets |
PAYEE_NOT_ALLOWED | Transfers to this payee are not allowed. | The payee, or the payee's institution, is not allowed to receive | No |
DUPLICATED_TRANSFER | A duplicated transfer was detected and rejected. | The same transfer was already sent | No — check the original Cash-Out first |
APPROVAL_REJECTED | The withdrawal was rejected during approval. | Rejected during manual approval | Only with a new approval |
PROCESSING_ERROR | The withdrawal could not be completed. The amount remains available in your account. | Any other failure | Yes — contact support if it repeats |
A retry is always a new POST /cash-out with a new externalReference: the cancelled Cash-Out keeps its reference, and reusing it returns 409 DUPLICATED_EXTERNAL_REFERENCE.
Fired when a previously completed Cash-Out is returned asynchronously by the payee's bank via SPI. The money comes back to your transactional balance. data.status is REFUNDED, with reason and refundedAt.
reason | Meaning |
|---|---|
PAYEE_REQUESTED | The payee asked their bank to return the transfer |
FRAUD | Returned because of fraud, including a MED (Special Return Mechanism) claim |
OPERATIONAL_ERROR | Returned because of an operational error at a bank |
REVERSED_BY_INSTITUTION | Reversed by the financial institution |
OTHER | Any other reason |
{
"eventType": "CASHOUT_REFUNDED",
"eventAt": "2026-03-11T09:30:20Z",
"data": {
"transactionId": "dd30446e-6cc5-4664-bf3f-6b7f5e55a1a9",
"internalCode": "IVKPRMOCDY",
"externalReference": "withdraw_xyz789",
"endToEndId": "D00416968202603101827cemeFscF6AG",
"originalEndToEndId": "E00416968202603101827cemeFscF6AG",
"status": "REFUNDED",
"reason": "PAYEE_REQUESTED",
"amount": 500.00,
"payee": {
"name": "Maria Silva",
"document": "12345678909",
"destination": {
"type": "PIX",
"pixKeyType": "CPF",
"pixKey": "12345678909"
},
"bankData": {
"ispb": "00000000",
"bank": "Banco do Brasil S.A.",
"branch": "0001",
"account": "123456"
}
},
"settlement": {
"ispb": "87654321",
"bank": "SETTLEMENT PARTNER B",
"branch": "0001",
"account": "654987"
},
"requestedAt": "2026-03-10T14:00:00-03:00",
"completedAt": "2026-03-10T14:02:30-03:00",
"refundedAt": "2026-03-11T09:30:15-03:00"
}
}The PIX Health engine (see GET /health/pix) watches the PIX network every minute and confirms an incident when the same anomaly repeats in two consecutive 5-minute windows. Every confirmed incident in your scope is pushed to you as a webhook, so you do not have to poll.
These events are not tied to a transaction and are therefore never sent to a request's webhookUrl. To receive them, register an additional endpoint in the panel under Integrations → Webhooks and subscribe to the PIX_INCIDENT_* event types. Same envelope, same headers, same X-Connect-Signature HMAC with your CryptoToken.
- An incident on a settlement partner is sent only if you route traffic through that partner. If you had no traffic at all in the window, the partners you hold an active account with count instead — see "If you stop trading" below.
- An incident on a payer bank is sent only if your payers pay from (or your payees receive at) that bank.
- An incident on your own operation (
MERCHANT) is sent only to you. - Incidents on ConnectPSP or on the PIX system are sent to every subscriber.
A partner you are not routing through being down is not your problem, so you will not hear about it — even if you hold an account there. You can still see its health in GET /health/pix: it is listed with routing: false, carrying its real status, and it never affects your overallStatus.
Some integrators deliberately pause routing for a while to watch how the network behaves before switching back. That keeps working: while you send no traffic, your scope falls back to the partners you hold an active account with, and you keep receiving their incidents — plus ConnectPSP and PIX system incidents, which everyone gets.
An account counts as active while it has been seen in the last 30 days. Accounts are never removed from our records, so a partner you have not held an account with for over 30 days drops out of your scope entirely: no webhooks, and no entry in GET /health/pix.
PIX_INCIDENT_DETECTED → PIX_INCIDENT_UPDATED (0..n) → PIX_INCIDENT_RESOLVED| Event | When |
|---|---|
PIX_INCIDENT_DETECTED | The incident was just confirmed (first time only). |
PIX_INCIDENT_UPDATED | Its status or severity changed (for example ACTIVE → WORSENING, or WORSENING → RECOVERING). |
PIX_INCIDENT_RESOLVED | It was closed after 3 consecutive normal windows (once). |
Use data.incidentId to correlate the three. The same id is accepted by GET /health/pix/incidents/{id} if you want the evidence.
{
"eventType": "PIX_INCIDENT_DETECTED",
"eventAt": "2026-09-08T15:38:00.412Z",
"data": {
"incidentId": "LIVE-202609081530-PAYER_INSTITUTION-00360305",
"status": "ACTIVE",
"severity": "DEGRADED",
"confidence": "MEDIUM",
"originClass": "INFERRED",
"origin": {
"type": "PAYER_INSTITUTION",
"id": "00360305",
"displayName": "CAIXA ECONOMICA FEDERAL"
},
"operation": "CASH_IN",
"startedAt": "2026-09-08T15:30:00.000Z",
"detectedAt": "2026-09-08T15:37:59.000Z",
"recoveredAt": null,
"resolvedAt": null,
"updatedAt": "2026-09-08T15:37:59.000Z",
"affectedPartners": 2,
"summary": "Forte evidência de degradação em cobranças pagas por clientes da CAIXA ECONOMICA FEDERAL: a conversão desses pagadores ficou em 78,5%, contra 93,0% esperados, enquanto 9 instituições de controle seguem normais."
}
}| Field | Meaning |
|---|---|
status | ACTIVE, WORSENING, RECOVERING or RESOLVED. |
severity | DEGRADED, SEVERELY_DEGRADED or UNAVAILABLE — how bad. |
confidence | LOW, MEDIUM or HIGH — how sure the engine is about who is at fault. |
originClass | OBSERVED (we saw the error), INFERRED (deduced from what did not happen), CORROBORATED / CONFIRMED (backed by a second signal). |
origin.type | CONNECTPSP, BANKING_PARTNER, PAYER_INSTITUTION, PAYER_COHORT, MERCHANT or PIX_SYSTEMIC. |
origin.id | ISPB for partners and banks, your customer id for MERCHANT, ALL for the platform. |
operation | CASH_IN or CASH_OUT. |
affectedPartners | How many settlement partners the incident reached. |
summary | Diagnosis in Portuguese, written for you. Never contains other clients' data. |
Timestamps are UTC with milliseconds (...sss Z).
Same data shape. Look at status and severity — that is what changed.
Same data shape with status: "RESOLVED" and resolvedAt filled.
- Treat
DETECTEDas "start watching", not as "stop sending PIX". Most incidents areDEGRADEDand the platform keeps working. UNAVAILABLEon a settlement partner you route through is the one worth paging on.- Idempotency:
X-Event-Idis new per attempt, butdata.incidentId+eventType+data.updatedAtidentifies a unique notification.
| Event | Trigger | Delivered to |
|---|---|---|
CASHIN_REQUESTED | Cash-In created, awaiting payment | Additional endpoints only |
CASHIN_PAID | PIX received and confirmed by BACEN/SPI | webhookUrl + additional endpoints |
CASHIN_REFUNDED | A paid Cash-In was fully refunded | webhookUrl + additional endpoints |
CASHIN_EXPIRED | The Cash-In QR Code expired without payment | webhookUrl + additional endpoints |
CASHIN_FAILED | Cash-In failed before settlement (rare) | webhookUrl + additional endpoints |
CASHIN_UPDATED | Fallback for uncommon Cash-In state changes (rare) | webhookUrl + additional endpoints |
CASHOUT_REQUESTED | Cash-Out accepted for processing | Additional endpoints only |
CASHOUT_COMPLETED | Cash-Out settled successfully | webhookUrl + additional endpoints |
CASHOUT_FAILED | Cash-Out cancelled — money never left (status: CANCELLED) | webhookUrl + additional endpoints |
CASHOUT_REFUNDED | A completed Cash-Out was returned by the payee's bank | webhookUrl + additional endpoints |
PIX_INCIDENT_DETECTED | A PIX Health incident in your scope was confirmed | Additional endpoints only |
PIX_INCIDENT_UPDATED | Its status or severity changed | Additional endpoints only |
PIX_INCIDENT_RESOLVED | It was closed | Additional endpoints only |
Additional endpoints only receive the event types they subscribe to in the panel.