Gareth Wilson Gareth Wilson

Guide to Asaas Webhooks: Features and Best Practices

Published


Asaas is a Brazilian payments platform for charges by Pix, boleto and card, subscriptions, invoices and transfers. Its webhooks tell your application when a charge is paid, a subscription renews, a transfer settles or your account status changes, so your systems can react straight away instead of polling the API.

This guide covers how Asaas webhook configurations work, how to check the asaas-access-token header, the events and payloads you will receive, and how Asaas's retry penalty and paused queues affect you in production.

What are Asaas webhooks?

An Asaas webhook is a configuration with a URL, a list of events, a send type and an authentication token. When a selected event occurs, Asaas sends a POST with a JSON body describing the event and the resource it relates to. Each account can have up to 10 webhooks, with different URLs and events.

Asaas doesn't sign payloads. Instead, you choose an authToken when you create the webhook, and Asaas sends it in the asaas-access-token header of every notification. Your endpoint compares the header with the stored value.

Each webhook also has a send type. SEQUENTIALLY delivers events in the order they occurred, and NON_SEQUENTIALLY delivers them in parallel with no ordering guarantee.

Asaas webhook features

FeatureDetails
ConfigurationWeb app (Integrations > Webhooks) or POST /v3/webhooks
Limit10 webhooks per account
EnvironmentsSandbox and production are configured separately
AuthenticationauthToken you choose, sent in asaas-access-token
Token rules32 to 255 characters, no whitespace, no simple sequences, not an API key
SignatureNone
Event fieldevent in the body
Send typesSEQUENTIALLY (ordered) or NON_SEQUENTIALLY (parallel)
Delivery guaranteeAt least once
Retries15 attempts with increasing intervals, then the queue is interrupted
RetentionUndelivered events are kept for 14 days
RedirectsNot followed
LogsWebhook Logs, with payload, status code, attempts and errors

Common events

Asaas groups events by resource. Each webhook subscribes to the individual events it needs:

ResourceExample events
ChargesPAYMENT_CREATED, PAYMENT_CONFIRMED, PAYMENT_RECEIVED, PAYMENT_OVERDUE, PAYMENT_REFUNDED
SubscriptionsSUBSCRIPTION_CREATED
InvoicesINVOICE_CREATED
TransfersTRANSFER_CREATED
Bill paymentsBILL_PAID
AnticipationsRECEIVABLE_ANTICIPATION_CREDITED
Account statusACCOUNT_STATUS_COMMERCIAL_INFO_APPROVED
CheckoutCHECKOUT_CREATED
Balance blocksBALANCE_VALUE_BLOCKED
Internal movementsINTERNAL_TRANSFER_DEBIT
API keysACCESS_TOKEN_CREATED
Automatic PixPIX_AUTOMATIC_RECURRING_AUTHORIZATION_ACTIVATED, PIX_AUTOMATIC_RECURRING_AUTHORIZATION_CANCELLED

Mobile phone recharges have their own events too. Each resource's page in Asaas's docs lists its full set.

Every event shares an envelope of id, event and dateCreated, plus an object for the resource. Here is Asaas's PAYMENT_RECEIVED example:

{
  "id": "evt_123456789",
  "event": "PAYMENT_RECEIVED",
  "dateCreated": "2026-06-19 14:30:00",
  "payment": {
    "object": "payment",
    "id": "pay_080225913252",
    "customer": "cus_000005913252",
    "value": 150.00,
    "netValue": 148.35,
    "billingType": "PIX",
    "status": "RECEIVED"
  }
}

The resource key changes with the event: payment, subscription, invoice, transfer and so on. Some events, such as transfers, also include an account object. id identifies the event and stays the same on redelivery, so use it to deduplicate. dateCreated has no time zone.

Setting up Asaas webhooks

  1. Generate a token of 32 to 255 characters with no whitespace, for example a long random string. Don't reuse an Asaas API key.

  2. Create the webhook in the web app under Integrations > Webhooks, or through the API:

    POST /v3/webhooks
    
    {
      "name": "Payments Webhook",
      "url": "https://your-app.com/webhooks/asaas",
      "email": "integrations@example.com",
      "enabled": true,
      "interrupted": false,
      "apiVersion": 3,
      "authToken": "a-random-token-of-at-least-32-characters",
      "sendType": "SEQUENTIALLY",
      "events": ["PAYMENT_RECEIVED", "PAYMENT_OVERDUE"]
    }
    
  3. Store the token, for example as ASAAS_WEBHOOK_TOKEN. Asaas returns it only in the creation response, and later queries don't include it. Store the webhook's id too, so you can update or reset it later.

  4. Test in the sandbox: trigger an event, such as creating a charge, and confirm your endpoint receives it. Then repeat the configuration in production, because the two environments are independent.

  5. Check Webhook Logs for the status code and response of each attempt.

Securing Asaas webhooks

Compare asaas-access-token with your stored token in constant time:

const crypto = require('crypto');
const express = require('express');

function verifyAsaasToken(tokenHeader, expectedToken) {
  if (!tokenHeader || !expectedToken) return false;
  const received = Buffer.from(tokenHeader, 'utf8');
  const expected = Buffer.from(expectedToken, 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/asaas', express.raw({ type: '*/*' }), (req, res) => {
  const token = process.env.ASAAS_WEBHOOK_TOKEN;
  if (!token) return res.sendStatus(500); // fail closed

  if (!verifyAsaasToken(req.get('asaas-access-token'), token)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  // Persist event.id first, then respond and process event.event asynchronously.
  res.status(200).json({ status: 'SUCCESS' });
});

The same check in Python:

import hmac
import os


def verify_asaas_token(token_header, expected_token: str) -> bool:
    if not token_header or not expected_token:
        return False
    return hmac.compare_digest(token_header.encode("utf-8"), expected_token.encode("utf-8"))


# In FastAPI:
# verify_asaas_token(request.headers.get("asaas-access-token"), os.environ["ASAAS_WEBHOOK_TOKEN"])

Return exactly 200. Asaas's queue penalty page counts any 2xx as success, but its paused queue page says only 200 counts and that 201 and 204 are failures. Webhooks configured with an apiVersion below 3 also need SUCCESS in the response body, which is why the example returns {"status": "SUCCESS"}.

Make Asaas webhooks production-ready. Hookdeck Event Gateway checks the asaas-access-token header, answers Asaas with a 200 straight away, and holds every payment event for retry and replay so your queue never gets paused.

In Hookdeck, create an Asaas source and enter the same token. Hookdeck compares it with asaas-access-token on each request. Verification is optional on the source, so leave verification off if you didn't set an authToken. Hookdeck's default response is a 200 with a JSON body that includes "status": "SUCCESS".

Asaas webhook limitations and pain points

Failures pause the whole queue

The Problem: A few hours of errors stops all deliveries for that webhook, and events older than 14 days are deleted.

Why It Happens: Asaas retries with growing intervals: immediately, then after 30 seconds, 1, 3.5, 5, 15 and 25 minutes, then hourly, then two and three hours. After 15 consecutive failures the queue is interrupted. New events keep being generated and stored, but nothing is sent until you reactivate the queue.

Workarounds:

  • Act on the alert emails Asaas sends at attempts 5, 10 and 15.
  • Fix the cause, then use Remove penalty (or POST /v3/webhooks/{id}/removeBackoff) to skip the remaining wait. An interrupted queue needs to be reactivated separately.
  • Don't automate penalty removal. Asaas rate-limits that endpoint to discourage it.

How Hookdeck Can Help: Hookdeck answers Asaas with a 200 as soon as it stores the event, so your service's errors don't count against Asaas's queue. Hookdeck then retries delivery to your service on its own schedule and lets you replay anything that failed.

Sequential delivery blocks on one bad event

The Problem: One event that your endpoint keeps rejecting holds up every event after it.

Why It Happens: In SEQUENTIALLY mode, events wait for the previous one to be delivered. If PAYMENT_CONFIRMED is penalised, PAYMENT_RECEIVED waits behind it.

Workarounds:

  • Use NON_SEQUENTIALLY when your logic doesn't depend on order, and use dateCreated and the resource's status to resolve conflicts.
  • Never return an error for an event you can't process yet. Store it and return 200.

How Hookdeck Can Help: Hookdeck accepts each event in order, so Asaas's queue keeps moving. Failed deliveries to your service are retried individually rather than blocking the stream.

A shared token, not a signature

The Problem: The token proves who sent the request, but doesn't protect the body.

Why It Happens: Asaas sends the same static header with every notification and computes nothing over the payload.

Workarounds:

  • Use a long random token and HTTPS.
  • Allowlist Asaas's official IPs, remembering that the sandbox may use extra addresses.
  • For high-value actions, fetch the resource from the Asaas API before acting.

How Hookdeck Can Help: Only Hookdeck needs the Asaas token. Your services receive events from Hookdeck and don't need to store or compare it.

Redirects and firewalls cause silent failures

The Problem: Deliveries fail even though the endpoint works in a browser.

Why It Happens: Asaas doesn't follow redirects, so a 301 from HTTP to HTTPS or a trailing-slash redirect fails. A WAF that challenges unknown clients returns 403.

Workarounds:

  • Register the final HTTPS URL.
  • Add allow rules for Asaas's IPs in your firewall or WAF.

How Hookdeck Can Help: Hookdeck gives Asaas a stable HTTPS URL that doesn't redirect, and its request log shows exactly what arrived.

Best practices

Persist, respond, then process

Asaas's own guidance is to store the event, return 200, and run business logic asynchronously. See why to process webhooks asynchronously.

Deduplicate on the event id

Delivery is at least once in both send types. Keep a record of processed id values and skip repeats. See our guide to webhook idempotency.

Subscribe only to what you use

Asaas recommends configuring only the events your integration needs. With 10 webhooks per account, you can also split payment events and account events across separate URLs.

Monitor Webhook Logs

Check the logs for rising error counts before a queue gets interrupted, and resolve any paused queue within 14 days.

Conclusion

Asaas webhooks deliver payment, subscription, transfer and account events at least once, authenticated by a token in the asaas-access-token header. Compare the token in constant time, deduplicate on id, return 200 quickly, and watch your queue: 15 consecutive failures pause it, and events are deleted after 14 days.

Hookdeck Event Gateway keeps Asaas's queue moving by acknowledging every event, then retries delivery to your service until it succeeds.

Get started with Hookdeck for free and handle Asaas webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.