Agora
Providers

AbacatePay

Pix and boleto, no card. HMAC-SHA256 webhooks and a BRL-only, decimal-reais API.

  • Methods: pix, boleto, undefined — no credit card (routing credit_card here throws at the manager, not at the gateway).
  • Setup: payments.abacate({ apiKey, webhookSecret })ABACATE_API_KEY / ABACATE_PUBLIC_KEY.
  • Webhooks: HMAC-SHA256 (base64) over the raw body in x-webhook-signature, verified timing-safe against webhookSecret (the dashboard's "public key"). Events checkout.completed, checkout.refunded, checkout.disputed, the matching transparent.* trio, and subscription.completed/renewed/cancelled.
  • Idempotency: AbacatePay has none — refund, createCustomer, createSubscription and updateSubscription throw on an idempotencyKey rather than dropping it.
  • Subscriptions: recurring — no transparent checkout for the recurring charge yet.
  • Transparent checkout: charge uses POST /v2/transparents/create — PIX and Boleto inline, no redirect. The response carries the QR (brCodeBase64pixQrCodeImage, brCodepixCode) or the barcode (urlhostedUrl). Needs the payer's name + taxId — pass them via the charge's customer block or create the gateway customer with them.

Webhook verification is required at boot

This driver reports webhookVerification: 'unconfigured' — and the app refuses to boot — when neither webhookSecret nor publicKey is configured (env fallback ABACATE_PUBLIC_KEY). Set it before you deploy, or — only when verification genuinely happens upstream — name this provider in allowUnverifiedWebhooks. An empty credential slot is not "skip verification": with nothing to check against, POST /payments/webhook/abacate would accept any body anyone posted to it — including one that marks a payment paid. The app refuses to start instead. See Configuration.

Chargebacks

checkout.disputed and transparent.disputed — "Disputa/chargeback aberta em um checkout" and "…em um pagamento transparente", one per charge flow — normalize to payment.disputed, not payment.failed. A failed payment says the money never moved; a chargeback says it moved and came back. Only the second one means an account's access has to be reconsidered, and a row that says failed never raises that question.

Those two events are AbacatePay's entire dispute vocabulary. The published event list — checkout.*, transparent.*, subscription.*, transfer.*, payout.* — has nothing before a dispute and nothing after it, so this driver emits neither payment.dispute_warning nor payment.dispute_closed here, and a test keeps a later edit from inventing one.

Opened is the only dispute signal AbacatePay sends

There is no fraud alert, no retrieval request and no "a chargeback is incoming" notification, so nothing warns you while a refund would still stop the chargeback being filed. And there is no *.dispute_won / *.dispute_lost event and no dispute value in the billing status enum, so the outcome never reaches you as a webhook either. Both ends are reconciliation work.

Two things AbacatePay's reference does not answer

It does not say whether the funds are withdrawn when *.disputed fires — the event list gives one line per event and there is no payload example for these two — and it publishes no response deadline anywhere, so the normalized event carries no actionableUntil. The mapping stays at payment.disputed rather than being demoted to a warning on a guess: a filed dispute is what that event names. If you need to know where the money is, read event.raw.data and the billing's own status.

Idempotency: AbacatePay has none

No endpoint documents an idempotency header or field — the only request header on any of them is Authorization, and AbacatePay's own guidance mentions idempotency solely for consuming webhooks (dedupe on the event id). So refund, createCustomer, createSubscription and updateSubscription throw when handed an idempotencyKey: silently dropping it would turn a caller's retry guarantee into a second refund.

charge() still accepts one, as it always has, but not as deduplication — it is a legacy fallback for externalReference, sent as the transparent checkout's externalId.

app/services/checkout_service.ts
await payments.driver('pix').charge({
  customerId: 'cus_123',
  amount: 1990,
  customer: { name: 'Jane Doe', taxId: '123.456.789-00' },
  invoice: true,
})

On this page