Skip to content

Webhooks -- Overview

Webhooks allow your application to receive real-time notifications about events on the AvivPay platform. When an event occurs, AvivPay sends an HTTP POST to the registered URL.

How It Works

  1. Register a webhook URL in your account
  2. When an event occurs (e.g., PIX received), AvivPay sends an HTTP POST to your URL
  3. Your application processes the notification and responds with 2xx status (200, 201, or 204)

Available Events

AvivPay delivers PIX, TEF between AvivPay accounts, and operational webhook test events. Other products (boleto, accounts, STA) are out of scope. Any attempt to subscribe to events outside the table below is rejected with events: contains invalid events: ....

EventStatus bodyDescriptionDispatch
pix.charge.createdcreatedQR code generated or cash-in initiatedActive
pix.charge.paidpaidPIX received and settledActive
pix.charge.expiredexpiredQR code expired without paymentActive
pix.charge.cancelledcancelledQR code cancelled before paymentRegistered, not yet dispatched
pix.payout.queuedqueuedPIX send queued by operational limit. Automatic retry with max TTL 2hActive
pix.payout.processingprocessingPIX sent, awaiting BACEN confirmationActive
pix.payout.heldprocessingOutbound PIX held for review at the settlement agent (authorization/anti-fraud queue). It settles or rejects afterwards - do not resubmitActive
pix.payout.confirmedsettledPIX sent and confirmed (terminal)Active
pix.payout.failedrejectedPIX send rejected by SPI (terminal)Active
pix.payout.returnedreturnedSent PIX was returnedActive
pix.refund.requestedrequestedRefund request received (BACEN infraction); preventive block created on the client balanceActive
pix.refund.completedsettled / completedDefense analysis finalized and refund executed (or released)Active
pix.refund.failedfailedRefund rejected by the SPI/settlement participant (terminal); carries the reason_code and reason_descriptionActive
pix.return.receivedsettledPIX return received (credit)Active
pix.infraction.createdACKNOWLEDGEDPIX infraction reported by the counterparty through BACEN DICT; may require MED analysis and defenseActive
pix.infraction.resolvedCLOSED / CANCELLEDInfraction resolved by final decision or counterparty cancellationActive
pix.infraction.defense_submitteddefense_submittedDefense submitted by the merchant (portal or API); awaiting BACEN analysisActive
tef.transfer.sentsettledTEF between AvivPay accounts settled for the origin accountActive
tef.transfer.receivedsettledTEF between AvivPay accounts settled for the destination accountActive
tef.transfer.failedfailedTEF between AvivPay accounts rejected or not settledActive
webhook.testtestManual test. Available only via Admin/Merchant portal - the External API does not expose an endpoint to dispatch testsManual dispatch (not External API)

pix.charge.cancelled is not yet dispatched

The event is in the enum and can be subscribed to, but the system does not have a QR code cancellation flow today. If you subscribe, POST /webhooks responds 201 normally - but no notification will arrive. Continue monitoring pix.charge.expired for the QR's natural lifecycle.

Security

Each notification includes security and identification headers for validation:

HeaderDescription
X-AvivPay-SignatureHMAC-SHA256 signature of the payload (prefix sha256=). In rare cases (webhook registered without secret), the literal value is unsigned - see note below
X-AvivPay-TimestampUnix timestamp in seconds of the delivery
X-AvivPay-Event-IdUnique delivery UUID (for deduplication)
X-AvivPay-Event-TypeEvent type (e.g., pix.charge.paid)
Content-TypeAlways application/json
User-AgentAlways AvivPay-Webhook/1.0 - use for whitelisting in firewalls/WAF. Future evolution will follow the pattern AvivPay-Webhook/{version}; filter by prefix AvivPay-Webhook/ if you want to be immune to new versions

Signature unsigned when webhook has no secret

If the webhook was registered without a secret field in a legacy record, the X-AvivPay-Signature header may be unsigned. This disables HMAC validation on your side. If you receive unsigned, register a new webhook with an explicit secret and remove the old one.

SHA256 for webhooks vs SHA512 for the API

The API uses HMAC-SHA512 to authenticate requests you send. Webhooks sent by AvivPay use HMAC-SHA256 in the X-AvivPay-Signature signature. They are different algorithms -- each in its context.

Validating the Signature

Validate the signature to guarantee the notification was sent by AvivPay:

javascript
const crypto = require('crypto');

function validateWebhook(rawBody, timestamp, signature, secret) {
  // rawBody is the RAW request body string (before any JSON parse)
  // timestamp is unix seconds (e.g., 1712160000)
  const message = `${timestamp}.${rawBody}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Use the RAW body, not re-serialized

You must use the exact HTTP request body as the bytes arrived at your application. If you do JSON.parse and then JSON.stringify, the resulting bytes will not be identical to what AvivPay used for signing, and validation will fail.

In Express/Node: use express.raw({ type: 'application/json' }) or capture the body before any parsing middleware.

In other frameworks: configure your stack to capture the raw body before JSON middleware.

Key ordering in WEBHOOKS: NOT required

For webhook validation (HMAC-SHA256) you do NOT need to sort keys - use the raw body as received in the HTTP request from AvivPay.

⚠️ Attention - difference vs sending requests: In the HMAC-SHA512 signature of REQUESTS you send, alphabetical key ordering IS mandatory (the AvivPay server reorders before validating). Do not confuse the two scenarios:

  • Webhook received (HMAC-SHA256): validate the raw body without reordering
  • Request sent (HMAC-SHA512): sort your keys alphabetically before signing

Always validate

Never process a webhook without validating the signature. This protects against forged requests.

Additionally, validate that X-AvivPay-Timestamp is within ± 5 minutes of the current time (anti-replay protection - the server does not reject "old" webhooks by default; this check is up to your endpoint as defense-in-depth) and deduplicate events by X-AvivPay-Event-Id (protection against retries).

Event Ordering

Events for the same payment may reach your endpoint out of order within short windows (HTTP deliveries are independent and a PIX can settle in under a second). Handle statuses per the table:

Event typeSemantics
*.processing / intermediateInformational. It must never overwrite a terminal status you already received.
*.confirmed, *.paid, *.completed, *.failedTerminal (absorbing): once received, ignore any later intermediate event for the same payment.

Practical rules for your consumer:

  1. Apply state transitions based on the event content (the status field), not on arrival order.
  2. A payment in a terminal state never regresses: discard a processing received after confirmed/failed.
  3. Deduplicate by X-AvivPay-Event-Id (retries resend the same event with the same id).

Retry Policy

If your URL returns a status different from 2xx (or times out after 30 s), AvivPay performs up to 8 attempts with exponential backoff. The total time between the first and the eighth attempt is approximately 7h45min:

AttemptDelay since previous attemptCumulative time
1st- (immediate)~50-200 ms
2nd30 seconds~30 s
3rd2 minutes~2.5 min
4th10 minutes~12.5 min
5th30 minutes~42.5 min
6th1 hour~1.75 h
7th2 hours~3.75 h
8th4 hours~7.75 h

After 8 attempts without success, the webhook_delivery is marked with status failed and is not automatically resent. You can request a manual replay from AvivPay support providing the X-AvivPay-Event-Id (or have an operator with admin access replay via the portal).

Delivery status

Each delivery goes through statuses: pending (created, awaiting delivery) → delivered (2xx received) OR failed (8 attempts exhausted) OR expired (replay protection).

About expired: when a delivery attempt is too old before it can be sent, it may be discarded and marked as expired. This prevents late reprocessing from firing outdated notifications. Manual replays requested to AvivPay support follow a controlled flow and may resend the event to the client.

Durability

Before attempting the first delivery, the event is persisted for retry. If there is a failure during delivery, the system resumes automatically on the next retry - no event is lost.

Idempotency

Your application must be idempotent: if it receives the same event more than once (identified by X-AvivPay-Event-Id), it must process it without duplicating effects.

Manual replay via admin

If a delivery failed and you need to resend, the AvivPay team can perform a manual replay via the admin dashboard. Contact support with the delivery event_id.

Duplicate deliveries (known race condition)

The system may use more than one delivery path to accelerate the first notification and keep durable retry. In high-concurrency scenarios, you may receive the same payload twice via HTTP, but with the same X-AvivPay-Event-Id - it is the same event, not a retry.

To avoid duplicate impact:

  • Dedupe by X-AvivPay-Event-Id (recommended - unique UUID per delivery, stable across retries and in the race condition above)
  • Or alternatively dedupe by end_to_end_id + event_type when it makes sense for the event

This is expected behavior, not an error. Legitimate retries (after 5xx/timeout) also reuse the same X-AvivPay-Event-Id.

External ID in Webhooks

When a transaction was created with external_id, this field is included in the webhook payload within the data object. Use it to correlate the event with the order in your system without needing an additional query.

Endpoint Requirements

  • The URL must use HTTPS (unless allow_insecure: true on registration)
  • Must respond with 2xx status within 30 seconds
  • The response body is ignored
  • Recommended to respond fast (200 OK immediately) and process the event asynchronously on your side; long delays reduce throughput and increase chance of retries

Next Steps

AvivPay Instituição de Pagamento - ISPB 04838403