Breaking changes to the payloads and events on this page may still happen. When one does, we announce it in advance β on the Changelog and in Eulen's partner announcements group on Telegram β so you have time to adapt before it takes effect.
A webhook is a way for our system to send real-time events to your server via HTTP requests, without your server needing to ask for the event. You register a URL that will receive events right after they happen β a deposit clears, a withdrawal completes, or a MED is filed against a past deposit.See also: Possible Statuses
Registration is self-service. On your Telegram channel with Eulen:
/registerwebhook <type> <url> <secret>
Type: What kind of event data will be sent. Currently, webhook type can be:
deposit
withdraw
med
URL: The URL that events will be sent to. It must start with https:// β plain http://, localhost, and private address ranges (10.*, 172.*, 192.168.*) are rejected, so a tunnel with a public HTTPS hostname is the way to test against a local server.
Secret: You should create a secret of at least 16 characters. For maximum security, you can generate 32 random bytes as hex to use as a secret. You can do this by running this command on your terminal: openssl rand -hex 32.
/deletewebhooks <type> removes every registration of that type for your account and answers There are no webhooks registered for this type. when there was nothing to remove. Use it on its own to turn a webhook off.
Never leave two URLs registered for the same type
Registering a second URL for a type you already registered is accepted, and the reply still says β Webhook registered successfully β but it is not fan-out. Only one of the two endpoints receives the events, which one is not defined, and it can change between deliveries.Always /deletewebhooks <type> before registering the replacement. During a migration, run the old and the new endpoint behind a single URL on your side.
Every delivery carries the secret you registered, verbatim, after Basic :
Authorization: Basic <your-secret>
The value is not re-encoded β whatever string you gave to /registerwebhook is exactly what arrives. That matters if your framework parses the header as standard HTTP Basic auth, because a hex secret is not valid Base64 credentials and the parse will fail.If you want the standard form, register the Base64 of username:passwordas the secret. For example, partner:your-password becomes cGFydG5lcjp5b3VyLXBhc3N3b3Jk, and that is the string you register.To encode the credentials in Base64, you can use this command on your terminal: echo -n 'username:password' | base64
Compare the header in constant time and reject anything that does not match. The secret is the only proof that a delivery came from us.
A 202 Accepted, a 204 No Content, a redirect, or any other status is recorded as a failed delivery β even though your server processed the event fine. If you acknowledge before processing, answer 200, not 202.
Please remember to:
β οΈ Verify the Authorization header to ensure the request is coming from the correct source.
Return 200 OK (or 201) on successful processing.
Return a response within 15 seconds. After that the connection is dropped and the delivery counts as failed.
Use the deposits API as a fallback in case the webhook fails.
Keep the handler fast: do the heavy lifting (database writes, downstream calls) in a background job, and still answer 200.Process idempotently. Use a stable identifier from the payload (qrId for deposits, id for withdrawals) as a dedupe key, and make a repeat delivery a no-op.
One delivery attempt, with a single exception
For every event on this page but one, there is no automatic retry. If your endpoint is down, slow, or answers anything other than 200/201, that event is not redelivered β you get a warning message in your Telegram channel with Eulen, and nothing else.The exception is the deposit webhook announcing that a deposit was refunded. That one is delivered by a durable job, which retries a failed send for a limited number of attempts with growing intervals between them, so the same refund can reach you more than once. It changes nothing about what you build: dedupe on qrId as described above and a repeat is a no-op.Reconciliation is mandatory rather than optional: a periodic sweep with GET /deposits is the only way to recover an event you missed. Outside that one case, do not build a design that waits for a second attempt.
Delivery order
Deposit webhooks for the same payment are delivered in order. Withdraw and MED webhooks are not ordered, and a status you already passed may still arrive after a newer one β compare against the state you have stored before overwriting it.
Discriminate on webhookType
Every webhook body carries a webhookType field. Switch on it to route the payload to the right handler. The three values are "deposit", "withdraw", and "med".
Sent when a deposit reaches a terminal or notable state. The status field carries the same values as GET /deposit-status, and the payload mirrors that response plus the webhookType discriminator. The full set of values is on Possible Statuses.
When a delayed deposit will be processed, ISO 8601.
rejectionReasons
array of strings
Codes explaining why the deposit was held or refused: PAST_DAILY_LIMIT, BLOCKED_USER, HIGH_VELOCITY, PAYER_MISMATCH. The field is absent when none of them applies.
Recommended confirmation status
For crediting your user, confirm on approved β the funds have been received and cleared, and DePix will be sent shortly. This fires sooner than depix_sent.
Sent as a withdrawal (DePix β Pix payout) moves through its lifecycle. Mirrors the GET /withdraw-status response, plus the webhookType discriminator.
WithdrawWebhookBody
Field
Type
Description
webhookType
string
Always "withdraw".
id
string
Withdrawal ID (the withdrawalId returned by POST /withdraw).
Amount of BRL paid out, in cents. Not always the value returned by POST /withdraw: when the DePix received diverges from what the withdrawal expected, it is recalculated from what actually arrived. See below.
blockchainTxID
string
Liquid Network transaction ID for the DePix transfer.
The Liquid address you declared on POST /withdraw for returning the DePix if the payout cannot be completed, echoed back so a typo is visible before a refund needs it. The field is absent when you did not supply one.
The DePix you send to fund a withdrawal is compared against the amount that withdrawal expects.
Within 5%, in either direction, the withdrawal proceeds and the payout is recalculated from the amount that actually arrived. payoutAmountInCents on this webhook is the recalculated value, not the one POST /withdraw returned. Reconcile against this field, never against the value you stored at creation.
Above 5%, the deposit is ignored and the withdrawal is left untouched. You are notified on Telegram, and the notification names the withdrawal id and the transaction id.
A recalculated payout is checked again against the R$ 2,00 to 6.000,00 withdrawal range. Falling outside it also ignores the deposit, which is why a deposit slightly under the expected amount can be dropped: after fees there is less than the minimum left.
When the deposit is ignored and you declared a refundAddress, the DePix is returned to it automatically, in full and with no fee withheld, and the withdrawal moves to refunded. No withdraw event is sent for this return: the announcement is on Telegram and it names the Liquid transaction id, so read GET /api/withdraw-status if you reconcile from the API. Without a refundAddress the withdrawal is left as it is and can still be funded by a new deposit of the correct amount.
Sent when a MED (Pix special refund / chargeback) is filed against a past deposit. Use it to reconcile the affected deposit on your side.
No enablement step
med is a webhook type like the other two: register it with /registerwebhook med <url> <secret> and the events start arriving. There is nothing to switch on, and nothing to ask for.
MEDWebhookBody
Field
Type
Description
webhookType
string
Always "med".
qrId
string
ID of the deposit the MED was filed against.
bankTxId
string
Bank transaction ID of the original Pix payment.
blockchainTxID
string
Liquid Network transaction ID for the original DePix transfer.
creationDateReport
string
When the MED was filed, ISO 8601.
euid
string
EUID associated with the deposit.
name
string
Name tied to the report.
partnerId
string
Your Partner ID.
principalValueInCents
integer
Principal amount recorded for the MED, in cents of BRL. It is not always the amount in dispute β reconcile against the deposit it names, not against this figure alone.
Webhook delivery is best-effort and single-attempt, so reconciliation is part of a correct integration rather than a safety net you add later. If a delivery is missed β your endpoint was down, a 200 never reached us, or the response took longer than 15 seconds β pull the state directly.
GET /deposits β list deposits over a date range, filtered by status. Returns up to 200 rows of { qrId, status, bankTxId }. This is the documented fallback for sweeping over deposits you may have missed.
A periodic sweep with GET /deposits (for example, over the last few hours) is a reliable safety net. Treat your reconciliation job and your webhook handler as two paths to the same idempotent state machine.