Webhooks

Juno's webhooks provide real-time status updates for transactions on your account. By using them, you'll be notified automatically about events related to:

  • Issuances
  • Redemptions
  • Crypto Deposits
  • Crypto Withdrawals
  • Conversions

To receive these updates, you must register a public callback URL with Juno. Whenever one of the supported events changes its status, Juno will send a notification to your URL using an HTTP POST method. This notification includes a payload with the relevant transaction data.

📘

Important Considerations

Out-of-Order Events: Webhook events may not arrive in chronological order. Always use the created_at and updated_at timestamps to manage events correctly, rather than relying on the order of delivery.

Transaction events: Juno sends webhooks for transaction events. This single event type delivers information for all four types of transactions listed above.

Timeouts: Your callback URL must respond within five seconds to avoid a timeout.

After the URL receives the update, you must store it and process it later. Callback URLs that take more than five seconds to respond will be considered as a time out.


Getting Started

Registering Your Callback URL

To register your callback URL, make an HTTP POST call to the endpoint below. Make sure your URL is publicly accessible so Juno's webhooks can reach it.

POST https://stage.buildwithjuno.com/v4/webhooks

Body Parameters

  • callback_url (string): The public URL where you want to receive webhook notifications.
  • events (array[string]): A list of event types to subscribe to. The only accepted value is TRANSACTION.

Example Request:

{
  "callback_url": "https://example.com/juno-webhooks",
  "events": ["TRANSACTION"]
}

Successful Response: If successful, you will receive a response confirming the registration of your URL.

{
    "message": "Webhook successfully created",
    "webhooks": [
        {
            "id": 11330,
            "callback_url": "https://example.com/juno-webhooks",
            "event": "TRANSACTION"
        }
    ]
}

Failure Response

If there's an error, you'll receive a response with a message in the error field detailing the reason for the failure.

{
    "success": false,
    "error": "Duplicated subscription for event types: [transaction]"
}

Before you can register a webhook, Juno verifies your identity. Please refer to the Authentication guide for detailed instructions.


Understanding the Payloads

When a transaction event occurs, Juno sends a JSON object to your registered callback URL. This object contains two main fields: event and payload.

Common Transaction Fields

All transaction types share a set of common fields, which provide essential information about the event.

  • id (UUID): A unique identifier for the transaction.
  • type (string): The type of transaction, which can be ISSUANCE, REDEMPTION, CRYPTO_DEPOSIT, or CRYPTO_WITHDRAWAL.
  • status (string): The current status of the transaction. Possible values are PENDING, IN_PROGRESS, COMPLETE, or FAILED.
  • created_at (ISO 8601): The timestamp of when the transaction was created.
  • updated_at (ISO 8601): The timestamp of the last update to the transaction.

Transaction-Specific Payloads

The payload's structure changes based on the transaction type. The following sections detail the unique fields for each type.


Issuance

For issuance transactions, the payload includes an issuance object with additional details.

  • network (string): The payment network used, such as ARBITRUM, POLYGON.
  • amount (string): The amount of money credited to the Juno account. This is a positive number.
  • method (string): The method of transfer, either SPEI or BITSO_TRANSFER.
  • asset (string): The fiat settlement asset of the transaction (for example, MXN for SPEI-based MXNB issuances or BRL for Bitso Transfer-based BRL1 issuances). Note that the on-chain stablecoin (e.g., MXNB or BRL1) may differ from the value of this field.
  • deposit_sender_clabe (string): The sender's CLABE account number. Present for SPEI-based issuances (MXNB).
  • deposit_sender_name (string): The name of the sender. Present for SPEI-based issuances (MXNB).
  • deposit_receiver_clabe (string): The CLABE account number of the receiver, which is the AUTO_PAYMENT CLABE. Present for SPEI-based issuances (MXNB).
  • refunded (boolean): Indicates if the transaction has been refunded.
  • crypto_source_address (string): The blockchain address from which the crypto was sent.
  • crypto_destination_address (string): The blockchain address the crypto was sent to. This field is only present for COMPLETE transactions.
  • crypto_tx_hash (string): The unique transaction ID on the blockchain. This field is only present for COMPLETE transactions.
  • failure_reason (string): The reason for a failed transaction.
  • external_fiat_id (string): External fiat id for the issuance. This field can be null if the issuance is not initiated by a SPEI transfer.
  • refund_status (string): Overall status of the refund flow for this issuance. Only present when a refund has been triggered (for example, when the issuance fails after the deposit has been received). Possible values are PENDING, IN_PROGRESS, COMPLETE, and FAILED. See Refund Steps for details.
  • refund_steps (array): Ordered list of the individual operations Juno performs to return funds to the sender. Only present when a refund has been triggered. Issuance refunds return funds via SPEI and produce fiat-only steps. See Refund Steps for the step shape and possible values.

Example MXNB Issuance Payload (via SPEI)

{
  "event": "TRANSACTION",
  "payload": {
    "type": "ISSUANCE",
    "id": "d441f9db-e2df-4ca5-9dab-7526efdcea86",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-08-04T15:19:22Z",
    "updated_at": "2025-08-04T15:20:14Z",
    "issuance": {
      "network": "ARBITRUM",
      "amount": "100",
      "method": "SPEI",
      "asset": "MXN",
      "deposit_sender_clabe": "646180115400230775",
      "deposit_sender_name": "Sender Name",
      "deposit_receiver_clabe": "710969000000431628",
      "refunded": false,
      "crypto_source_address": "0xc0ffee254729296a45a3885639AC7E10F9d54979",
      "crypto_destination_address": "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E",
      "crypto_tx_hash": "0xfb98c58faf63d17ce0fd5d41e82c3ae2f9b3721f2c1e369c17b379b4b59fe062",
      "external_fiat_id": "TESTSPEI671271752263456670"
    }
  }
}

Example BRL1 Issuance Payload (via Bitso Transfer)

{
  "event": "TRANSACTION",
  "payload": {
    "type": "ISSUANCE",
    "id": "a12bc3d4-5e6f-7890-ab12-cd34ef567890",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-10-15T10:22:11Z",
    "updated_at": "2025-10-15T10:23:05Z",
    "issuance": {
      "network": "POLYGON",
      "amount": "500",
      "method": "BITSO_TRANSFER",
      "asset": "BRL",
      "refunded": false,
      "crypto_source_address": "0xc0ffee254729296a45a3885639AC7E10F9d54979",
      "crypto_destination_address": "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E",
      "crypto_tx_hash": "0xab12cd34ef567890ab12cd34ef567890ab12cd34ef567890ab12cd34ef567890"
    }
  }
}

Example MXNB Issuance Refund Payload (via SPEI)

Sent when a SPEI-based issuance is refunded back to the sender — for example, when the deposit is received but the mint fails downstream. The refund_status and refund_steps fields describe the state of the return-of-funds flow; the top-level status continues to reflect the issuance itself.

{
  "event": "TRANSACTION",
  "payload": {
    "type": "ISSUANCE",
    "id": "7bf6a851-bc53-49fd-b837-64b4a839b145",
    "status": "FAILED",
    "created_at": "2026-05-18T15:08:20Z",
    "updated_at": "2026-05-18T15:08:56Z",
    "issuance": {
      "network": "ARBITRUM",
      "amount": "99",
      "method": "SPEI",
      "asset": "MXN",
      "deposit_sender_clabe": "646180115400230775",
      "deposit_sender_name": "Test sender",
      "deposit_receiver_clabe": "710969123456324311",
      "refunded": true,
      "crypto_source_address": "0x4ee44Fa73C4534C4Bb61a06DD28Bd0aA49d4568a",
      "error": {
        "code": "insufficient_deposit_amount_for_issuance",
        "message": "Deposit amount is insufficient for token issuance"
      },
      "external_fiat_id": "TESTSPEI671271779116891495",
      "refund_status": "COMPLETE",
      "refund_steps": [
        {
          "step": "SPEI_WITHDRAWAL",
          "status": "COMPLETE",
          "id": "0179c64216044d0573e24bb9e4531616"
        }
      ]
    }
  }
}

Redemption

For redemption transactions, the payload includes an redemption object with additional details.

  • network (string): The payment network used, such as ARBITRUM, POLYGON.
  • amount (string): The amount of tokens to be redeemed. This is a positive number.
  • method (string): The method of transfer, either SPEI or BITSO_TRANSFER.
  • asset (string): The fiat settlement asset of the transaction (for example, MXN for SPEI-based MXNB redemptions or BRL for Bitso Transfer-based BRL1 redemptions). Note that the on-chain stablecoin (e.g., MXNB or BRL1) may differ from the value of this field.
  • refunded (boolean): Indicates if the transaction has been refunded.
  • receiver_account (object): Present for SPEI-based redemptions (MXNB). Contains the destination bank account details.
    • receiver_account.type (string): The type of the receiver account, for example, MEXICAN_BANK_ACCOUNT.
    • receiver_account.mexican_bank_account.clabe (string): The CLABE account where the redeemed tokens will be sent.
    • receiver_account.mexican_bank_account.external_fiat_id (string): External fiat id for the redemption. This field can be null if the redemption doesn't involve an SPEI transfer.
  • failure_reason (string): The reason for a failed transaction.
  • refund_status (string): Overall status of the refund flow for this redemption. Only present when a refund has been triggered (for example, when the on-chain settlement transfer fails after tokens have been burned). Possible values are PENDING, IN_PROGRESS, COMPLETE, and FAILED. See Refund Steps for details.
  • refund_steps (array): Ordered list of the individual operations Juno performs to return funds to the user. Only present when a refund has been triggered. Redemption refunds may include both a fiat leg (e.g. returning fiat to the user's settlement account) and crypto legs (re-minting or transferring the burned tokens back), so the list can contain a mix of step types. See Refund Steps for the step shape and possible values.

Example MXNB Redemption Payload (via SPEI)

{
  "event": "TRANSACTION",
  "payload": {
    "type": "REDEMPTION",
    "id": "d441f9db-e2df-4ca5-9dab-7526efdcea86",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-08-04T15:19:22Z",
    "updated_at": "2025-08-04T15:20:14Z",
    "redemption": {
      "network": "ARBITRUM",
      "amount": "100",
      "method": "SPEI",
      "asset": "MXN",
      "refunded": false,
      "receiver_account": {
        "type": "MEXICAN_BANK_ACCOUNT",
        "mexican_bank_account": {
          "clabe": "012180001234567890",
          "external_fiat_id": "202508049071015060000005610295"
        }
      }
    }
  }
}

Example BRL1 Redemption Payload (via Bitso Transfer)

{
  "event": "TRANSACTION",
  "payload": {
    "type": "REDEMPTION",
    "id": "b23cd4e5-6f78-9012-bc34-de56fa789012",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-10-14T14:05:33Z",
    "updated_at": "2025-10-14T14:06:12Z",
    "redemption": {
      "network": "POLYGON",
      "amount": "1000",
      "method": "BITSO_TRANSFER",
      "asset": "BRL",
      "refunded": false,
      "receiver_account": {
        "type": "BITSO_TRANSFER"
      }
    }
  }
}

Example MXNB Redemption Refund Payload (via SPEI)

Sent when a SPEI-based redemption is refunded back to the user — for example, when the on-chain settlement transfer fails after the user-side tokens have been burned. The refund_steps list can contain both fiat and crypto entries because reversing a redemption may require returning fiat to the settlement account and re-minting or transferring the burned tokens back to the user.

{
  "event": "TRANSACTION",
  "payload": {
    "type": "REDEMPTION",
    "id": "b2c5e9c3-08eb-4d2e-9a2b-8561d31e5c45",
    "status": "FAILED",
    "created_at": "2026-05-15T13:28:13Z",
    "updated_at": "2026-05-15T13:32:07Z",
    "redemption": {
      "network": "ARBITRUM",
      "amount": "101",
      "method": "SPEI",
      "asset": "MXN",
      "refunded": true,
      "error": {
        "code": "transaction_failed",
        "message": "Transaction failed due to an unexpected error"
      },
      "receiver_account": {
        "type": "MEXICAN_BANK_ACCOUNT",
        "mexican_bank_account": {
          "clabe": "646180115400230775"
        }
      },
      "refund_status": "COMPLETE",
      "refund_steps": [
        {
          "step": "BITSO_TRANSFER",
          "status": "COMPLETE",
          "id": "07254396856dfc6e7b8d1ed0b0369d9f"
        },
        {
          "step": "TOKEN_MINT",
          "status": "COMPLETE",
          "id": "0x4602daec67a7557bf289c324cde63af980840d2198ec65f1a8f44f3bb39b0ee6"
        },
        {
          "step": "TOKEN_TRANSFER",
          "status": "COMPLETE",
          "id": "0x06c81168fe2384b6a5328a4ed2a8df881472593d9595f0c335026f1b2d82f981"
        },
        {
          "step": "TOKEN_DEPOSIT",
          "status": "COMPLETE",
          "id": "52b57799cf03fe5b728c842b4b3a0389"
        }
      ]
    }
  }
}

Refund Steps

When a refund is triggered for an issuance or redemption, Juno surfaces it through two fields on the corresponding issuance or redemption object: refund_status (the overall state of the refund flow) and refund_steps (the individual operations that make it up). Both fields are only present once a refund has actually been triggered — successful, non-refunded transactions omit them entirely.

refund_status

Tracks the overall refund flow independently of the transaction's top-level status. It uses the same vocabulary as the rest of the webhook contract:

  • PENDING: A refund has been triggered but has not yet started executing.
  • IN_PROGRESS: At least one refund step is being processed.
  • COMPLETE: All refund steps finished successfully and funds have been returned.
  • FAILED: The refund flow ended in a non-recoverable error. Some steps may still appear as COMPLETE — see the individual refund_steps entries to understand how far the refund progressed.

The webhook is delivered every time refund_status transitions (including the first null → PENDING transition), in addition to the regular transitions on the top-level status field. Each transition produces a distinct webhook so consumers can track the refund's progress in real time.

refund_steps

An ordered list of the discrete operations performed during the refund. Each entry has the following shape:

  • step (string): The kind of operation being performed. The possible values depend on the transaction type:
    • Issuance refunds (SPEI return of funds to the sender):
      • BITSO_TRANSFER — Move funds from the AUTO_PAYMENT CLABE into the user's NVIO account.
      • SPEI_WITHDRAWAL — Send the funds out via SPEI to the original sender's CLABE.
    • Redemption refunds (return of funds to the user):
      • BITSO_TRANSFER — Move fiat from the user's NVIO account back to the Juno account.
      • TOKEN_MINT — Re-mint the burned tokens to compensate the user.
      • TOKEN_TRANSFER — Transfer the re-minted tokens to the user's destination address.
      • TOKEN_DEPOSIT — Re-deposit crypto that was withdrawn during the original redemption flow.
  • status (string): State of this individual step. Possible values: IN_PROGRESS, COMPLETE, FAILED.
  • id (string): Bitso/Juno-side identifier (FID/WID for fiat legs, on-chain transaction hash for crypto legs) used to reconcile the step against your records. The field is omitted while the step is IN_PROGRESS and is back-filled by a follow-up webhook once the underlying ledger or chain operation reports completion. Steps that did not produce an external operation (e.g. some FAILED legs) may never receive an id.

The list is ordered by the creation timestamp of each step, so it can be consumed as a chronological log of what Juno did to return the funds. Not every refund will exercise every step — for example, an issuance refund only emits fiat steps, while a redemption refund may emit any combination of fiat and crypto steps depending on which leg of the original flow needed to be reversed.


Crypto Deposit

For Crypto Deposit transactions, the payload includes a crypto_deposit object with additional details.

  • network (string): The payment network used, such as ARBITRUM.
  • amount (string): The amount received in the specified asset. This is a positive number.
  • asset (string): The asset of the transaction.
  • crypto_source_address (string): The blockchain address from which the crypto was sent.
  • crypto_destination_address (string): The blockchain address the crypto was sent to.
  • crypto_tx_hash (string): The unique transaction ID on the blockchain. This field is only present for COMPLETE transactions.
  • failure_reason (string): The reason for a failed transaction.

Example Crypto Deposit Payload

{
  "event": "TRANSACTION",
  "payload": {
    "type": "CRYPTO_DEPOSIT",
    "id": "d441f9db-e2df-4ca5-9dab-7526efdcea86",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-08-04T15:19:22Z",
    "updated_at": "2025-08-04T15:20:14Z",
    "crypto_deposit": {
      "network": "ARBITRUM",
      "amount": "100",
      "asset": "MXNB",
      "crypto_source_address": "0xc0ffee254729296a45a3885639AC7E10F9d54979",
      "crypto_destination_address": "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E",
      "crypto_tx_hash": "0xfb98c58faf63d17ce0fd5d41e82c3ae2f9b3721f2c1e369c17b379b4b59fe062"
    }
  }
}

Crypto Withdrawal

For Crypto Withdrawal transactions, the payload includes an crypto_withdrawal object with additional details.

  • network (string): The payment network used, such as ARBITRUM.
  • amount (string): The amount to be sent in the specified asset. This is a positive number.
  • asset (string): The asset of the transaction.
  • crypto_source_address (string): The blockchain address from which the crypto was sent.
  • crypto_destination_address (string): The blockchain address the crypto was sent to.
  • crypto_tx_hash (string): The unique transaction ID on the blockchain. This field is only present for COMPLETE transactions.
  • failure_reason (string): The reason for a failed transaction.

Example Crypto Withdrawal Payload

{
  "event": "TRANSACTION",
  "payload": {
    "type": "CRYPTO_WITHDRAWAL",
    "id": "d441f9db-e2df-4ca5-9dab-7526efdcea86",
    "status": "COMPLETE",
    "external_ref": "user-ref-12345",
    "created_at": "2025-08-04T15:19:22Z",
    "updated_at": "2025-08-04T15:20:14Z",
    "crypto_withdrawal": {
      "network": "ARBITRUM",
      "amount": "100",
      "asset": "MXNB",
      "crypto_source_address": "0xc0ffee254729296a45a3885639AC7E10F9d54979",
      "crypto_destination_address": "0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E",
      "crypto_tx_hash": "0xfb98c58faf63d17ce0fd5d41e82c3ae2f9b3721f2c1e369c17b379b4b59fe062"
    }
  }
}

Conversions

For Conversion transactions, the payload includes an conversion object with additional details.

  • quote_id (string): The unique identifier for the quote used to execute the conversion.
  • amount_from (string): The amount of the source asset used in the conversion.
  • amount_to (string): The resulting amount of the destination asset after the conversion.
  • asset_from (string): The source asset that was sold (e.g., MXNB).
  • asset_to (string): The destination asset that was purchased (e.g., USDC).
  • exchange_rate (string): The exchange rate applied to the conversion.

Example Conversion Payload

{
  "event": "TRANSACTION",
  "payload": {
    "id": "6242a068-56a5-4f79-a3f3-fb4758d40db5",
    "type": "CONVERSION",
    "status": "COMPLETE",
    "created_at": "2025-10-02T12:39:34Z",
    "updated_at": "2025-10-02T12:39:34Z",
    "conversion": {
      "quote_id": "b5f26dc0-7b99-4b07-9d9f-5a8630a6d43f",
      "amount_from": "25000",
      "amount_to": "1298.3766240000",
      "asset_from": "MXNB",
      "asset_to": "USDC",
      "exchange_rate": "19.2548136942"
    }
  }
}

Transaction Statuses

Webhooks deliver real-time updates on the status of your transactions. To understand the possible states a transaction can be in, refer to the Transaction Status page. This document provides a complete breakdown of each status, including PENDING, IN_PROGRESS, COMPLETE, and FAILED.

📘

Important Considerations

While some PENDING or IN_PROGRESS statuses may not be delivered via Webhook for certain transactions, you will always receive an event for the final COMPLETE or FAILED status.


Best Practices for Using Webhooks

Follow these best practices to ensure your webhook endpoint is secure and reliable:

  • Handle Duplicate Events — Avoid Double Processing: Webhooks may send the same event multiple times, so managing potential duplicates is crucial. To prevent issues like double spending or duplicate credits, always verify both the transaction's final status and its unique transaction identifier. This verification ensures that each event is processed only once.
  • Process Events Asynchronously: Use an asynchronous queue instead of handling events synchronously. This prevents scalability issues, especially during traffic spikes, and ensures your endpoint's hosting infrastructure remains stable.
  • Acknowledge Events Quickly: Respond with a 2xx status code immediately upon receiving an event. Perform complex operations after acknowledging the event to avoid timeouts and lost events.
  • Validate Events Using IPs: For added security, only allow webhook requests from trusted sources by adding Juno's IP addresses to an allowlist.
EnvironmentIP Addresses
Stage3.129.233.228
3.22.247.241
3.134.133.168
Production52.15.91.227
18.216.72.107
18.219.140.132