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: Deposit Statuses & Withdraw 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.
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:b4b8de163b9582b8281efa0be7360caba585b1c5240504a24c6dae43f504f922 becomes cGFydG5lcjpiNGI4ZGUxNjNiOTU4MmI4MjgxZWZhMGJlNzM2MGNhYmE1ODViMWM1MjQwNTA0YTI0YzZkYWU0M2Y1MDRmOTIy, 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.
A failed delivery is not sent again
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.This makes reconciliation mandatory rather than optional: a periodic sweep with GET /deposits is the only way to recover an event you missed. 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 Deposit Statuses.
DepositWebhookBody
Field
Type
Description
webhookType
string
Always "deposit".
bankTxId
string
Bank transaction ID for the Pix payment.
blockchainTxID
string
Liquid Network transaction ID for the DePix transfer.
customerMessage
string
Human-readable message about the deposit.
payerName
string
Name of the payer.
payerTaxNumber
string
CPF/CNPJ of the payer.
pixKey
string
Pix key used for the payment.
qrId
string
ID of the deposit (the id returned by POST /deposit).
When a delayed deposit will be processed, ISO 8601.
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 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.
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.