# Checkout Deposits Webhook Real-time webhook notifications sent when deposits are completed via BlockBee's hosted deposit solution. These webhooks notify you when a customer has successfully made a deposit to your deposit link and you should credit their account. **Method:** You choose when creating the deposit link: - `GET` (default) - Webhook data sent as URL query parameters - `POST` (set `post=1`) - Webhook data sent in request body **Content-Type:** You choose when creating the deposit link: - `application/x-www-form-urlencoded` (default) - Standard form encoding - `application/json` (set `json=1`) - JSON format in request body --- ## Webhook Overview Unlike custom payment flow webhooks, checkout deposit webhooks are sent only once when the deposit is complete and confirmed. There are no separate pending and confirmed states - you receive a single notification when the deposit is ready to be processed. The webhook contains all the information you need to: - Verify the deposit was legitimate - Credit the customer's account - Store transaction details for your records - Track which user made the deposit (via custom parameters in notify_url) --- ## Webhook Fields - **`uuid`** (`string`) (required): Unique identifier for each deposit made by your clients. This can be used to track any duplicate webhooks sent, in case our system doesn't mark the webhook as successful. **Important:** You can use this unique identifier for idempotency to prevent processing duplicate webhooks. Store this UUID when processing deposits and check if it has already been processed. **Example:** `afe11bea-768b-47ae-ba0f-907379fbe5ef` - **`txid`** (`string`) (required): Transaction hash of your client's deposit. **Example:** `0xa7551df44e487f9c0507d68d90193cde2604dfcefdc975bae54535a2e0f80b32` - **`address`** (`string`) (required): Address generated by BlockBee where your client's deposit was received. **Example:** `3PFoGK63cVVUWnd2vu7W1kM83NXUfvzMqM` - **`payment_url`** (`string`) (required): Deposit link where the deposit came from. **Note:** Any query parameters you add to the notify_url (like `?user_id=12345`) will be included in the webhook payload, allowing you to track which user the deposit belongs to. **Example:** `https://pay.blockbee.io/deposit/fG78jtx96ugjtu0eIbeLmFB9z0feJf9N` - **`currency`** (`string`) (required): FIAT currency. Should be the same that you set in your Payment Settings at BlockBee's Dashboard. **Example:** `usd` - **`paid_amount`** (`string`) (required): Amount paid in cryptocurrency by the user. **Note:** The cryptocurrency/token used to make the payment is described in the parameter `paid_coin`. **Example:** `1.23` - **`received_amount`** (`string`) (required): Value forwarded to you, after fees deducted. **Note:** The cryptocurrency/token used to make the payment is described in the parameter `paid_coin`. **Example:** `9.24` - **`paid_amount_fiat`** (`string`) (required): Amount paid in the FIAT currency described in the parameter `currency`. **Example:** `21234.32` (represents $212.34) - **`received_amount_fiat`** (`string`) (required): Value forwarded to you, after fees deducted in the FIAT currency selected. **Note:** FIAT currency. Should be the same that you set in your Payment Settings at BlockBee's Dashboard. **Example:** `9.24` (represents $9.24) - **`paid_coin`** (`string`) (required): Cryptocurrency/token used to make the payment. **Note:** The amount paid will be available in the parameter `paid_amount`. **Format:** - Native coins: `btc`, `eth`, `ltc`, `bch`, `trx` - ERC-20 tokens: `erc20_usdt`, `erc20_usdc` - TRC-20 tokens: `trc20_usdt`, `trc20_usdc` - BEP-20 tokens: `bep20_usdt`, `bep20_usdc` - Polygon tokens: `polygon_usdt`, `polygon_usdc` **Example:** `btc` - **`exchange_rate`** (`number`) (required): Exchange rate at the time of the payment. **Example:** `20000` - **`type`** (`string`) (required): Type of the IPN. **Example:** `deposit` - **`status`** (`string`) (required): Status of the transaction. **Example:** `done` --- ## Security: Verify Webhook Signatures > **WARNING** >**Security Warning!** Always verify incoming webhook signatures. For a complete guide, see our **[Verify Webhook Signatures Guide](/webhooks/verify-webhook-signature)**. --- ## Reliability and Best Practices For important information on how BlockBee handles webhook delivery, retries, and timeouts, along with essential best practices for building a reliable webhook handler, please see our main guide. **➡️ [Read the How Webhooks Overview](/webhooks)** --- ## Implementation Examples ```javascript // Express.js webhook handler app.post('/webhook', express.json(), (req, res) => { // 1. Verify the webhook signature if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } // 2. Extract data from the payload const { uuid, paid_amount_fiat, paid_coin, txid, status, payment_url } = req.body; // 3. Check for duplicates using UUID if (isWebhookAlreadyProcessed(uuid)) { return res.status(200).send('*ok*'); } // 4. Verify the deposit is complete if (status !== 'done') { return res.status(200).send('*ok*'); } // 5. Extract user ID from payment URL (if you added custom parameters) const urlParams = new URLSearchParams(payment_url.split('?')[1]); const userId = urlParams.get('user_id'); // 6. Process the successful deposit // Credit user account, store transaction, etc. processSuccessfulDeposit({ userId, amount: paid_amount_fiat, coin: paid_coin, txid: txid, uuid: uuid }); // 7. Mark this webhook as processed using the UUID markWebhookAsProcessed(uuid); // Always respond to stop retries res.status(200).send('*ok*'); }); ``` ```php $userId, 'amount' => $paid_amount_fiat, 'coin' => $paid_coin, 'txid' => $txid, 'uuid' => $uuid ]); // 7. Mark this webhook as processed using the UUID markWebhookAsProcessed($uuid); // Always respond to stop retries http_response_code(200); echo '*ok*'; ?> ``` ```python # Flask webhook handler @app.route('/webhook', methods=['POST']) def webhook(): # 1. Verify the webhook signature if not verify_webhook_signature(request): return 'Unauthorized', 401 # 2. Extract data from the payload data = request.get_json() uuid = data.get('uuid') paid_amount_fiat = data.get('paid_amount_fiat') paid_coin = data.get('paid_coin') txid = data.get('txid') status = data.get('status') payment_url = data.get('payment_url') # 3. Use the uuid to prevent processing the same webhook twice if is_webhook_already_processed(uuid): return '*ok*', 200 # 4. Verify the deposit is complete if status != 'done': return '*ok*', 200 # 5. Extract user ID from payment URL (if you added custom parameters) from urllib.parse import urlparse, parse_qs parsed_url = urlparse(payment_url) query_params = parse_qs(parsed_url.query) user_id = query_params.get('user_id', [None])[0] # 6. Process the successful deposit # Credit user account, store transaction, etc. process_successful_deposit({ 'user_id': user_id, 'amount': paid_amount_fiat, 'coin': paid_coin, 'txid': txid, 'uuid': uuid }) # 7. Mark this webhook as processed using the UUID mark_webhook_as_processed(uuid) # Always respond to stop retries return '*ok*', 200 ``` ```ruby # Ruby on Rails webhook handler class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token def deposit # 1. Verify the webhook signature unless verify_webhook_signature(request) render plain: 'Unauthorized', status: :unauthorized return end # 2. Extract data from the payload uuid = params[:uuid] paid_amount_fiat = params[:paid_amount_fiat] paid_coin = params[:paid_coin] txid = params[:txid] status = params[:status] payment_url = params[:payment_url] # 3. Use the uuid to prevent processing the same webhook twice if webhook_already_processed?(uuid) render plain: '*ok*', status: :ok return end # 4. Verify the deposit is complete unless status == 'done' render plain: '*ok*', status: :ok return end # 5. Extract user ID from payment URL (if you added custom parameters) uri = URI(payment_url) query_params = URI.decode_www_form(uri.query || '') user_id = query_params.assoc('user_id')&.last # 6. Process the successful deposit # Credit user account, store transaction, etc. process_successful_deposit( user_id: user_id, amount: paid_amount_fiat, coin: paid_coin, txid: txid, uuid: uuid ) # 7. Mark this webhook as processed using the UUID mark_webhook_as_processed(uuid) # Always respond to stop retries render plain: '*ok*', status: :ok end end ``` ```csharp // ASP.NET Core webhook handler [ApiController] [Route("webhook")] public class WebhookController : ControllerBase { [HttpPost("deposit")] public async Task DepositWebhook([FromBody] DepositWebhookPayload payload) { // 1. Verify the webhook signature if (!VerifyWebhookSignature(Request)) { return Unauthorized("Unauthorized"); } // 2. Extract data from the payload var uuid = payload.Uuid; var paidAmountFiat = payload.PaidAmountFiat; var paidCoin = payload.PaidCoin; var txid = payload.Txid; var status = payload.Status; var paymentUrl = payload.PaymentUrl; // 3. Use the uuid to prevent processing the same webhook twice if (IsWebhookAlreadyProcessed(uuid)) { return Ok("*ok*"); } // 4. Verify the deposit is complete if (status != "done") { return Ok("*ok*"); } // 5. Extract user ID from payment URL (if you added custom parameters) var uri = new Uri(paymentUrl); var queryParams = HttpUtility.ParseQueryString(uri.Query); var userId = queryParams["user_id"]; // 6. Process the successful deposit // Credit user account, store transaction, etc. await ProcessSuccessfulDeposit(new DepositData { UserId = userId, Amount = paidAmountFiat, Coin = paidCoin, Txid = txid, Uuid = uuid }); // 7. Mark this webhook as processed using the UUID MarkWebhookAsProcessed(uuid); // Always respond to stop retries return Ok("*ok*"); } } public class DepositWebhookPayload { [JsonProperty("uuid")] public string Uuid { get; set; } [JsonProperty("paid_amount_fiat")] public string PaidAmountFiat { get; set; } [JsonProperty("paid_coin")] public string PaidCoin { get; set; } [JsonProperty("txid")] public string Txid { get; set; } [JsonProperty("status")] public string Status { get; set; } [JsonProperty("payment_url")] public string PaymentUrl { get; set; } } ``` ```java // Spring Boot webhook handler @RestController @RequestMapping("/webhook") public class WebhookController { @PostMapping("/deposit") public ResponseEntity depositWebhook(@RequestBody DepositWebhookPayload payload) { // 1. Verify the webhook signature if (!verifyWebhookSignature(request)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized"); } // 2. Extract data from the payload String uuid = payload.getUuid(); String paidAmountFiat = payload.getPaidAmountFiat(); String paidCoin = payload.getPaidCoin(); String txid = payload.getTxid(); String status = payload.getStatus(); String paymentUrl = payload.getPaymentUrl(); // 3. Use the uuid to prevent processing the same webhook twice if (isWebhookAlreadyProcessed(uuid)) { return ResponseEntity.ok("*ok*"); } // 4. Verify the deposit is complete if (!"done".equals(status)) { return ResponseEntity.ok("*ok*"); } // 5. Extract user ID from payment URL (if you added custom parameters) String userId = null; try { URI uri = new URI(paymentUrl); String query = uri.getQuery(); if (query != null) { String[] pairs = query.split("&"); for (String pair : pairs) { String[] keyValue = pair.split("="); if (keyValue.length == 2 && "user_id".equals(keyValue[0])) { userId = URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8); break; } } } } catch (Exception e) { log.error("Error parsing payment URL", e); } // 6. Process the successful deposit // Credit user account, store transaction, etc. processSuccessfulDeposit(new DepositData(userId, paidAmountFiat, paidCoin, txid, uuid)); // 7. Mark this webhook as processed using the UUID markWebhookAsProcessed(uuid); // Always respond to stop retries return ResponseEntity.ok("*ok*"); } } public class DepositWebhookPayload { private String uuid; private String paidAmountFiat; private String paidCoin; private String txid; private String status; private String paymentUrl; // Getters and setters public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } public String getPaidAmountFiat() { return paidAmountFiat; } public void setPaidAmountFiat(String paidAmountFiat) { this.paidAmountFiat = paidAmountFiat; } public String getPaidCoin() { return paidCoin; } public void setPaidCoin(String paidCoin) { this.paidCoin = paidCoin; } public String getTxid() { return txid; } public void setTxid(String txid) { this.txid = txid; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getPaymentUrl() { return paymentUrl; } public void setPaymentUrl(String paymentUrl) { this.paymentUrl = paymentUrl; } } ``` ```go // Go webhook handler package main import ( "encoding/json" "log" "net/http" "net/url" "strings" ) type DepositWebhookPayload struct { Uuid string `json:"uuid"` PaidAmountFiat string `json:"paid_amount_fiat"` PaidCoin string `json:"paid_coin"` Txid string `json:"txid"` Status string `json:"status"` PaymentURL string `json:"payment_url"` } func depositWebhookHandler(w http.ResponseWriter, r *http.Request) { // 1. Verify the webhook signature if !verifyWebhookSignature(r) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // 2. Extract data from the payload var payload DepositWebhookPayload if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // 3. Use the uuid to prevent processing the same webhook twice if isWebhookAlreadyProcessed(payload.Uuid) { w.WriteHeader(http.StatusOK) w.Write([]byte("*ok*")) return } // 4. Verify the deposit is complete if payload.Status != "done" { w.WriteHeader(http.StatusOK) w.Write([]byte("*ok*")) return } // 5. Extract user ID from payment URL (if you added custom parameters) userId := "" if parsedURL, err := url.Parse(payload.PaymentURL); err == nil { if queryParams := parsedURL.Query(); queryParams != nil { userId = queryParams.Get("user_id") } } // 6. Process the successful deposit // Credit user account, store transaction, etc. processSuccessfulDeposit(DepositData{ UserID: userId, Amount: payload.PaidAmountFiat, Coin: payload.PaidCoin, Txid: payload.Txid, Uuid: payload.Uuid, }) // 7. Mark this webhook as processed using the UUID markWebhookAsProcessed(payload.Uuid) // Always respond to stop retries w.WriteHeader(http.StatusOK) w.Write([]byte("*ok*")) } type DepositData struct { UserID string Amount string Coin string Txid string Uuid string } ``` ```bash # Test webhook with curl (for development/testing) curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -d '{ "uuid": "afe11bea-768b-47ae-ba0f-907379fbe5ef", "paid_amount_fiat": "21234.32", "paid_coin": "btc", "txid": "0xa7551df44e487f9c0507d68d90193cde2604dfcefdc975bae54535a2e0f80b32", "status": "done", "payment_url": "https://pay.blockbee.io/deposit/fG78jtx96ugjtu0eIbeLmFB9z0feJf9N?user_id=12345" }' # Expected response: *ok* ``` ### Key Security Checks 1. **Verify Webhook Signature** - Ensure the request is from BlockBee 2. **Check Deposit Status** - Only process when `status=done` 3. **Validate UUID** - Use the UUID for idempotency to prevent duplicate processing 4. **Extract User ID** - Use the `payment_url` to get your user identifier (if you added custom parameters) ### Best Practices - **Idempotency:** Use the `uuid` to ensure you only process each deposit once - **Respond Quickly:** Always respond with `*ok*` and a `200` status code - **Asynchronous Processing:** Handle long-running tasks in background jobs - **Error Handling:** Log errors but don't fail the webhook response - **Validation:** Verify all critical fields before processing the deposit ## Webhook Payload Example ```json { "uuid": "afe11bea-768b-47ae-ba0f-907379fbe5ef", "txid": "0xa7551df44e487f9c0507d68d90193cde2604dfcefdc975bae54535a2e0f80b32", "address": "3PFoGK63cVVUWnd2vu7W1kM83NXUfvzMqM", "payment_url": "https://pay.blockbee.io/deposit/fG78jtx96ugjtu0eIbeLmFB9z0feJf9N", "currency": "usd", "paid_amount": "1.23", "received_amount": "9.24", "paid_amount_fiat": "21234.32", "received_amount_fiat": "9.24", "paid_coin": "btc", "exchange_rate": 20000, "type": "deposit", "status": "done" } ```