# Custom Payment Flow Build a fully customized cryptocurrency payment experience with complete control over the user interface and payment flow. This guide will walk you through creating a payment from scratch using BlockBee's API. ![Example of BlockBee's Custom Payment Flow taken from WooCommerce Plugin](/assets/images/custom-flow-integration.jpg) *Example of BlockBee's Custom Payment Flow taken from WooCommerce Plugin* ## Overview The custom payment flow gives you maximum flexibility to create the exact payment experience you want. You'll handle: - **Payment creation** - Generate payment addresses and amounts - **User interface** - Build your own payment screens - **Webhook handling** - Process the webhooks with the payment confirmations > **INFO** >**Estimated time:** 30-45 minutes for a basic implementation ## Setup Before you can start building custom payment flows, you need to configure your BlockBee account and get your API credentials. ### 1. Create a BlockBee Account If you haven't already, [sign up for a BlockBee account](https://dash.blockbee.io/register) to get access to the dashboard. ### 2. Configure Addresses Set up the cryptocurrency addresses where you want to receive payments at [Addresses](https://dash.blockbee.io/profile/addresses). > **INFO** >Check this handy [tutorial](https://support.blockbee.io/support/solutions/articles/204000013598-how-to-set-up-the-addresses-) on how to setup your addresses. ### 3. Generate API Key 1. Navigate to the [API Keys section](https://dash.blockbee.io/profile/api-keys) in your dashboard 2. Click "Generate New API Key" 3. Copy the generated API key - you'll need this for all API requests > **INFO** >Check this handy [tutorial](https://support.blockbee.io/support/solutions/articles/204000013926-how-to-generate-a-new-api-key) on how to generate an API Key. > **WARNING** >**Keep your API key secure:** Never expose your API key in client-side code or public repositories. Store it securely in environment variables or server-side configuration. > **WARNING** >**Minimum Transaction Amounts:** Every cryptocurrency has minimum transaction amounts on different blockchains. Transactions below these minimums will be **ignored by BlockBee systems and funds will be lost**. Always check the minimum amounts for your chosen cryptocurrencies before accepting payments. You can find the complete list at [blockbee.io/cryptocurrencies](https://blockbee.io/cryptocurrencies) or fetch them using the [info endpoint](/api/tickerinfo). ## Step 1: Create Payment First, create a new payment by calling the BlockBee API. You'll need your API key and the payment details. ```javascript // Create a new payment const createPayment = async (orderId) => { const callbackUrl = encodeURIComponent('https://yoursite.com/webhook?order_id=' + orderId); const params = new URLSearchParams({ callback: callbackUrl, apikey: 'YOUR_API_KEY', address: 'YOUR_WALLET_ADDRESS', post: 0, json: 0, pending: 1, multi_token: 0, convert: 1 }); const response = await fetch(`https://api.blockbee.io/btc/create/?${params}`); const data = await response.json(); return data; }; ``` ```php $callbackUrl, 'apikey' => 'YOUR_API_KEY', 'address' => 'YOUR_WALLET_ADDRESS', 'post' => 0, 'json' => 0, 'pending' => 1, 'multi_token' => 0, 'convert' => 1 ]); $url = 'https://api.blockbee.io/btc/create/?' . $params; $result = file_get_contents($url); return json_decode($result, true); } ?> ``` ```python import requests from urllib.parse import quote def create_payment(order_id): callback_url = quote(f'https://yoursite.com/webhook?order_id={order_id}') params = { 'callback': callback_url, 'apikey': 'YOUR_API_KEY', 'address': 'YOUR_WALLET_ADDRESS', 'post': 0, 'json': 0, 'pending': 1, 'multi_token': 0, 'convert': 1 } response = requests.get('https://api.blockbee.io/btc/create/', params=params) return response.json() ``` ```ruby require 'net/http' require 'json' require 'uri' def create_payment(order_id) callback_url = URI.encode_www_form_component("https://yoursite.com/webhook?order_id=#{order_id}") params = { callback: callback_url, apikey: 'YOUR_API_KEY', address: 'YOUR_WALLET_ADDRESS', post: 0, json: 0, pending: 1, multi_token: 0, convert: 1 } query_string = URI.encode_www_form(params) uri = URI("https://api.blockbee.io/btc/create/?#{query_string}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) response = http.request(request) JSON.parse(response.body) end ``` ```csharp using System; using System.Net.Http; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; public class PaymentService { private static readonly HttpClient client = new HttpClient(); public async Task CreatePayment(string orderId) { var callbackUrl = HttpUtility.UrlEncode($"https://yoursite.com/webhook?order_id={orderId}"); var queryParams = HttpUtility.ParseQueryString(string.Empty); queryParams["callback"] = callbackUrl; queryParams["apikey"] = "YOUR_API_KEY"; queryParams["address"] = "YOUR_WALLET_ADDRESS"; queryParams["post"] = "0"; queryParams["json"] = "0"; queryParams["pending"] = "1"; queryParams["multi_token"] = "0"; queryParams["convert"] = "1"; var url = $"https://api.blockbee.io/btc/create/?{queryParams}"; var response = await client.GetAsync(url); var result = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(result); } } ``` ```java import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import com.google.gson.Gson; import java.util.Map; public class PaymentService { private static final HttpClient client = HttpClient.newHttpClient(); private static final Gson gson = new Gson(); public Map createPayment(String orderId) { try { String callbackUrl = URLEncoder.encode( "https://yoursite.com/webhook?order_id=" + orderId, StandardCharsets.UTF_8 ); String url = "https://api.blockbee.io/btc/create/" + "?callback=" + callbackUrl + "&apikey=YOUR_API_KEY" + "&address=YOUR_WALLET_ADDRESS" + "&post=0" + "&json=0" + "&pending=1" + "&multi_token=0" + "&convert=1"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); return gson.fromJson(response.body(), Map.class); } catch (Exception e) { throw new RuntimeException("Payment creation failed", e); } } } ``` ```go package main import ( "encoding/json" "net/http" "net/url" "io/ioutil" "fmt" ) func createPayment(orderID string) (map[string]interface{}, error) { callbackURL := url.QueryEscape(fmt.Sprintf("https://yoursite.com/webhook?order_id=%s", orderID)) params := url.Values{} params.Add("callback", callbackURL) params.Add("apikey", "YOUR_API_KEY") params.Add("address", "YOUR_WALLET_ADDRESS") params.Add("post", "0") params.Add("json", "0") params.Add("pending", "1") params.Add("multi_token", "0") params.Add("convert", "1") apiURL := fmt.Sprintf("https://api.blockbee.io/btc/create/?%s", params.Encode()) resp, err := http.Get(apiURL) if err != nil { return nil, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } var result map[string]interface{} json.Unmarshal(body, &result) return result, nil } ``` ```bash # URL encode the callback parameter CALLBACK_URL=$(echo "https://yoursite.com/webhook?order_id=12345" | jq -rR @uri) curl -G "https://api.blockbee.io/btc/create/" \ --data-urlencode "callback=${CALLBACK_URL}" \ --data-urlencode "apikey=YOUR_API_KEY" \ --data-urlencode "address=YOUR_WALLET_ADDRESS" \ --data-urlencode "post=0" \ --data-urlencode "json=0" \ --data-urlencode "pending=1" \ --data-urlencode "multi_token=0" \ --data-urlencode "convert=1" ``` > **INFO: Using Our Official Libraries** >For a simpler integration, you can also use our official libraries. Here's how you would create a custom payment: > > ```javascript > // Using the official Node.js library > const BlockBee = require('@blockbee/api'); > > const callbackUrl = 'https://yoursite.com/webhook'; > const params = { > order_id: orderId, > }; > const blockbeeParams = { > post: 0, > json: 0, > pending: 1, > multi_token: 0, > convert: 1 > }; > > const bb = new BlockBee('btc', 'YOUR_WALLET_ADDRESS', callbackUrl, params, blockbeeParams, 'YOUR_API_KEY'); > const address = await bb.getAddress(); > // address.address_in > ``` > > ```php > // Using the official PHP library > $params = ['order_id' => $orderId]; > $blockbeeParams = [ > 'post' => 0, > 'json' => 0, > 'pending' => 1, > 'multi_token' => 0, > 'convert' => 1 > ]; > > $bb = new BlockBee\BlockBee('btc', 'YOUR_WALLET_ADDRESS', 'https://yoursite.com/webhook', $params, $blockbeeParams, 'YOUR_API_KEY'); > $address = $bb->get_address(); > // $address->address_in > ``` > > ```python > # Using the official Python library > from blockbee import BlockBeeHelper > > params = {'order_id': orderId} > bb_params = { > 'post': 0, > 'json': 0, > 'pending': 1, > 'multi_token': 0, > 'convert': 1 > } > > bb = BlockBeeHelper('btc', 'YOUR_WALLET_ADDRESS', 'https://yoursite.com/webhook', params, bb_params, 'YOUR_API_KEY') > address = bb.get_address() > # address['address_in'] > ``` > > You can find the full documentation for our libraries here: > - [Node.js Library](/libraries/nodejs) > - [PHP Library](/libraries/php) > - [Python Library](/libraries/python) Check the API Reference to understand all the parameters you can use with this endpoint. - [GET /{ticker}/create/](../api/tickercreate): Create New Payment Address **Parameter Notes:** * **`callback`** - Add query parameters to help track payments when receiving webhooks (e.g., `?order_id=123&user_id=456`) * **`address`** - Your wallet address where payments will be forwarded. Can be omitted if set in the [Dashboard](https://dash.blockbee.io/profile/addresses) * **`pending`** - Set to `1` to receive webhooks for unconfirmed transactions (recommended for better UX) * **`convert`** - Set to `1` to automatically convert received payments to your preferred currency * **`post`** - Set to `1` to receive webhooks via POST instead of GET requests (default is GET) * **`json`** - Set to `1` to receive webhook data in JSON format instead of URL-encoded parameters (works with both GET and POST) * **`multi_token`** - Only relevant for blockchains with tokens (Ethereum, Solana, Tron). Set to `1` to accept multiple token types **Payment Splitting:** To split payments between multiple addresses, use: `percentage_1@address_1|percentage_2@address_2` Example: `0.5@ADDRESS_1|0.5@ADDRESS_2` splits 50/50 between two addresses. **Response:** ```json { "address_in": "14PqCsA7KMgseZMPwg6mJy754MtQkrgszu", "address_out": "1H6ZZpRmMnrw8ytepV3BYwMjYYnEkWDqVP", "callback_url": "https://yoursite.com/webhook?order_id=12345", "priority": "default", "minimum_transaction_coin": 0.008, "status": "success" } ``` > **WARNING** >**Important:** Store both the `address_in` and `callback_url` values from the response: > - **`address_in`** - The unique payment address your customer will send cryptocurrency to > - **`callback_url`** - Acts as a permanent identifier that links to this specific payment setup > > **Address Persistence:** The `callback_url` works as an "ID" that permanently associates with the `address_out` (your destination address). Once set, the `address_out` cannot be changed for that callback URL, even if you provide a different address in future API calls. This ensures payment consistency and prevents accidental address changes. > > **Logs Integration:** The exact `callback_url` returned from this response can be used with the logs endpoint to track payments and transaction history without relying on webhooks. > > **To change the destination address:** You must use a different `callback_url` (e.g., add a version parameter like `?order_id=123&v=2`) to create a new payment setup with a different `address_out`. ## Step 2: Display Payment Information Create a payment interface that shows the customer how to complete their payment. You have two main scenarios depending on your use case: ### Scenario A: Exchange Deposits (Any Amount) For exchanges or services accepting any amount above the minimum, fetch the minimum transaction requirements and display them to users. ### Scenario B: Ecommerce Payments (Specific Amount) For stores with fixed prices, convert your fiat amount to cryptocurrency using real-time exchange rates. Let's implement both scenarios: > **INFO: Using Our Official Libraries** >You can also use our official libraries for these API calls. Here's how you would get ticker info, convert currencies, and generate QR codes: > > ```javascript > // Using the official Node.js library > const BlockBee = require('@blockbee/api'); > > // Get ticker information and minimums > const tickerInfo = await BlockBee.getInfo('btc', 'YOUR_API_KEY'); > // tickerInfo.minimum_transaction_coin > > // Convert USD to BTC for ecommerce payments > const conversion = await BlockBee.getConvert('btc', usdAmount, 'USD', 'YOUR_API_KEY'); > // conversion.value_coin > > // Get QR code for payment address > const qrCode = await BlockBee.getQrcode('btc', address, amount, size, 'YOUR_API_KEY'); > // qrCode.qr_code (base64 image) > ``` > > ```php > // Using the official PHP library > // Get ticker information and minimums > $tickerInfo = BlockBee\BlockBee::get_info('btc', 'YOUR_API_KEY'); > // $tickerInfo->minimum_transaction_coin > > // Convert USD to BTC for ecommerce payments > $conversion = BlockBee\BlockBee::get_convert('btc', $usdAmount, 'USD', 'YOUR_API_KEY'); > // $conversion->value_coin > > // Get QR code for payment address > $qrCode = BlockBee\BlockBee::get_qrcode('btc', $address, $amount, $size, 'YOUR_API_KEY'); > // $qrCode->qr_code (base64 image) > ``` > > ```python > # Using the official Python library > from blockbee import BlockBeeHelper > > # Get ticker information and minimums > ticker_info = BlockBeeHelper.get_info('btc', 'YOUR_API_KEY') > # ticker_info['minimum_transaction_coin'] > > # Convert USD to BTC for ecommerce payments > conversion = BlockBeeHelper.get_convert('btc', usd_amount, 'USD', 'YOUR_API_KEY') > # conversion['value_coin'] > > # Get QR code for payment address > qr_code = BlockBeeHelper.get_qrcode('btc', address, amount, size, 'YOUR_API_KEY') > # qr_code['qr_code'] (base64 image) > ``` ```javascript // Get ticker information and minimums const getTickerInfo = async () => { const response = await fetch('https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY'); const data = await response.json(); return data; }; // Convert USD to BTC for ecommerce payments const convertAmount = async (usdAmount) => { const params = new URLSearchParams({ apikey: 'YOUR_API_KEY', value: usdAmount, from: 'USD' }); const response = await fetch(`https://api.blockbee.io/btc/convert/?${params}`); const data = await response.json(); return data.value_coin; // BTC amount }; // Get QR code for payment address const getQRCode = async (address, amount = null) => { const params = new URLSearchParams({ apikey: 'YOUR_API_KEY', address: address, size: 200 }); if (amount) { params.append('value', amount); } const response = await fetch(`https://api.blockbee.io/btc/qrcode/?${params}`); const data = await response.json(); return data.qr_code; // Base64 image }; // Display payment information - Exchange Deposits const displayExchangeDeposit = async (paymentData) => { const tickerInfo = await getTickerInfo(); const qrCode = await getQRCode(paymentData.address_in); const paymentContainer = document.getElementById('payment-container'); paymentContainer.innerHTML = `

Deposit Bitcoin

Minimum Deposit: ${tickerInfo.minimum_transaction_coin} BTC

Send Bitcoin to:

${paymentData.address_in}
Payment QR Code

⚠️ Send only amounts above ${tickerInfo.minimum_transaction_coin} BTC

⏳ Waiting for deposit...

`; }; // Display payment information - Ecommerce Payment const displayEcommercePayment = async (paymentData, usdAmount) => { const btcAmount = await convertAmount(usdAmount); const tickerInfo = await getTickerInfo(); const minimumAmount = tickerInfo.minimum_transaction_coin; // Validate minimum amount if (btcAmount < minimumAmount) { const paymentContainer = document.getElementById('payment-container'); paymentContainer.innerHTML = `

❌ Payment Amount Too Low

Your payment amount: ${btcAmount} BTC

Minimum required: ${minimumAmount} BTC

⚠️ Payments below the minimum will be lost. Please increase your order amount.

`; return; } const qrCode = await getQRCode(paymentData.address_in, btcAmount); const paymentContainer = document.getElementById('payment-container'); paymentContainer.innerHTML = `

Complete Your Payment

Amount: $${usdAmount} USD (${btcAmount} BTC)

Send exactly:

${btcAmount} BTC

To address:

${paymentData.address_in}
Payment QR Code

QR code includes amount and address

⚠️ Minimum transaction amount: ${minimumAmount} BTC

⏳ Waiting for payment...

`; }; const copyAddress = (address) => { navigator.clipboard.writeText(address); alert('Address copied to clipboard!'); }; const copyAmount = (amount) => { navigator.clipboard.writeText(amount); alert('Amount copied to clipboard!'); }; ``` ```php 'YOUR_API_KEY', 'value' => $usdAmount, 'from' => 'USD' ]); $url = 'https://api.blockbee.io/btc/convert/?' . $params; $result = file_get_contents($url); $data = json_decode($result, true); return $data['value_coin']; // BTC amount } // Get QR code for payment address function getQRCode($address, $amount = null) { $params = [ 'apikey' => 'YOUR_API_KEY', 'address' => $address, 'size' => 200 ]; if ($amount) { $params['value'] = $amount; } $url = 'https://api.blockbee.io/btc/qrcode/?' . http_build_query($params); $result = file_get_contents($url); $data = json_decode($result, true); return $data['qr_code']; // Base64 image } // Display payment information - Exchange Deposits function displayExchangeDeposit($paymentData) { $tickerInfo = getTickerInfo(); $qrCode = getQRCode($paymentData['address_in']); $address = htmlspecialchars($paymentData['address_in']); $minimum = $tickerInfo['minimum_transaction_coin']; echo "

Deposit Bitcoin

Minimum Deposit: $minimum BTC

Send Bitcoin to:

$address
Payment QR Code

⚠️ Send only amounts above $minimum BTC

⏳ Waiting for deposit...

"; } // Display payment information - Ecommerce Payment function displayEcommercePayment($paymentData, $usdAmount) { $btcAmount = convertAmount($usdAmount); $tickerInfo = getTickerInfo(); $minimumAmount = $tickerInfo['minimum_transaction_coin']; // Validate minimum amount if ($btcAmount < $minimumAmount) { echo "

❌ Payment Amount Too Low

Your payment amount: $btcAmount BTC

Minimum required: $minimumAmount BTC

⚠️ Payments below the minimum will be lost. Please increase your order amount.

"; return; } $qrCode = getQRCode($paymentData['address_in'], $btcAmount); $address = htmlspecialchars($paymentData['address_in']); echo "

Complete Your Payment

Amount: $$usdAmount USD ($btcAmount BTC)

Send exactly:

$btcAmount BTC

To address:

$address
Payment QR Code

QR code includes amount and address

⚠️ Minimum transaction amount: $minimumAmount BTC

⏳ Waiting for payment...

"; } ?> ``` ```python import requests # Get ticker information and minimums def get_ticker_info(): response = requests.get('https://api.blockbee.io/btc/info/', params={'apikey': 'YOUR_API_KEY'}) return response.json() # Convert USD to BTC for ecommerce payments def convert_amount(usd_amount): params = { 'apikey': 'YOUR_API_KEY', 'value': usd_amount, 'from': 'USD' } response = requests.get('https://api.blockbee.io/btc/convert/', params=params) data = response.json() return data['value_coin'] # BTC amount # Get QR code for payment address def get_qr_code(address, amount=None): params = { 'apikey': 'YOUR_API_KEY', 'address': address, 'size': 200 } if amount: params['value'] = amount response = requests.get('https://api.blockbee.io/btc/qrcode/', params=params) data = response.json() return data['qr_code'] # Base64 image # Display payment information - Exchange Deposits def display_exchange_deposit(payment_data): ticker_info = get_ticker_info() qr_code = get_qr_code(payment_data['address_in']) context = { 'address': payment_data['address_in'], 'minimum': ticker_info['minimum_transaction_coin'], 'qr_code': qr_code } return render_template('exchange_deposit.html', **context) # Display payment information - Ecommerce Payment def display_ecommerce_payment(payment_data, usd_amount): btc_amount = convert_amount(usd_amount) ticker_info = get_ticker_info() minimum_amount = ticker_info['minimum_transaction_coin'] # Validate minimum amount if btc_amount < minimum_amount: context = { 'error': True, 'payment_amount': btc_amount, 'minimum_amount': minimum_amount } return render_template('payment_error.html', **context) qr_code = get_qr_code(payment_data['address_in'], btc_amount) context = { 'address': payment_data['address_in'], 'usd_amount': usd_amount, 'btc_amount': btc_amount, 'qr_code': qr_code, 'minimum_amount': minimum_amount } return render_template('ecommerce_payment.html', **context) # exchange_deposit.html template """

Deposit Bitcoin

Minimum Deposit: {{ minimum }} BTC

Send Bitcoin to:

{{ address }}
Payment QR Code

⚠️ Send only amounts above {{ minimum }} BTC

⏳ Waiting for deposit...

""" # payment_error.html template """

❌ Payment Amount Too Low

Your payment amount: {{ payment_amount }} BTC

Minimum required: {{ minimum_amount }} BTC

⚠️ Payments below the minimum will be lost. Please increase your order amount.

""" # ecommerce_payment.html template """

Complete Your Payment

Amount: ${{ usd_amount }} USD ({{ btc_amount }} BTC)

Send exactly:

{{ btc_amount }} BTC

To address:

{{ address }}
Payment QR Code

QR code includes amount and address

⚠️ Minimum transaction amount: {{ minimum_amount }} BTC

⏳ Waiting for payment...

""" ``` ```ruby # Using Rails/Sinatra def display_payment(payment_data, amount) @address = payment_data['address_in'] @amount = amount # Get minimum transaction amount require 'net/http' require 'json' uri = URI("https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY") response = Net::HTTP.get(uri) ticker_info = JSON.parse(response) @minimum_amount = ticker_info['minimum_transaction_coin'] @qr_url = "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=#{@address}" erb :payment end # payment.erb template """

Complete Your Payment

Amount: <%= @amount %> USD

Send Bitcoin to:

<%= @address %>
Payment QR Code

⚠️ Minimum transaction amount: <%= @minimum_amount %> BTC

⏳ Waiting for payment...

""" ``` ```csharp // Using ASP.NET Core public class PaymentViewModel { public string Address { get; set; } public decimal Amount { get; set; } public string QrUrl { get; set; } public decimal MinimumAmount { get; set; } } public async Task DisplayPayment(dynamic paymentData, decimal amount) { // Get minimum transaction amount using var client = new HttpClient(); var response = await client.GetAsync("https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY"); var result = await response.Content.ReadAsStringAsync(); var tickerInfo = JsonConvert.DeserializeObject(result); var minimumAmount = tickerInfo.minimum_transaction_coin; var model = new PaymentViewModel { Address = paymentData.address_in, Amount = amount, QrUrl = $"https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={paymentData.address_in}", MinimumAmount = minimumAmount }; return View(model); } ``` ```java // Using Spring Boot @GetMapping("/payment") public String displayPayment(Model model, @RequestParam String address, @RequestParam double amount) { // Get minimum transaction amount RestTemplate restTemplate = new RestTemplate(); String url = "https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY"; ResponseEntity response = restTemplate.getForEntity(url, Map.class); Map tickerInfo = response.getBody(); double minimumAmount = (Double) tickerInfo.get("minimum_transaction_coin"); model.addAttribute("address", address); model.addAttribute("amount", amount); model.addAttribute("minimumAmount", minimumAmount); model.addAttribute("qrUrl", "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" + address); return "payment"; } ``` ```go // Using Go templates type PaymentData struct { Address string Amount float64 QrUrl string MinimumAmount float64 } func displayPayment(w http.ResponseWriter, address string, amount float64) { // Get minimum transaction amount resp, err := http.Get("https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY") if err != nil { http.Error(w, "Failed to get minimum amount", http.StatusInternalServerError) return } defer resp.Body.Close() var tickerInfo map[string]interface{} json.NewDecoder(resp.Body).Decode(&tickerInfo) minimumAmount := tickerInfo["minimum_transaction_coin"].(float64) data := PaymentData{ Address: address, Amount: amount, QrUrl: fmt.Sprintf("https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=%s", address), MinimumAmount: minimumAmount, } tmpl := template.Must(template.ParseFiles("payment.html")) tmpl.Execute(w, data) } ``` ```bash # Get minimum transaction amount first MINIMUM_AMOUNT=$(curl -s "https://api.blockbee.io/btc/info/?apikey=YOUR_API_KEY" | jq -r '.minimum_transaction_coin') # Static HTML example with minimum amount echo '

Complete Your Payment

Amount: $AMOUNT USD

Send Bitcoin to:

$ADDRESS
Payment QR Code

⚠️ Minimum transaction amount: '"'"'$MINIMUM_AMOUNT'"'"' BTC

⏳ Waiting for payment...

' > payment.html ``` ### Key API Endpoints Used The examples above integrate with these BlockBee API endpoints: 1. **[GET /{ticker}/info/](../api/tickerinfo)** - Get minimum transaction amounts and ticker information 2. **[GET /{ticker}/convert/](../api/tickerconvert)** - Convert fiat amounts to cryptocurrency 3. **[GET /{ticker}/qrcode/](../api/tickerqrcode)** - Generate QR codes with optional amount embedding ### Implementation Notes **For Exchange Deposits:** - Fetch minimum transaction requirements using the info endpoint - Display address-only QR codes (no fixed amount) - **Always display minimum deposit requirements prominently** - this is critical to prevent fund loss - Accept any amount above the minimum - **Warn users that amounts below minimum will be lost** **For Ecommerce Payments:** - Convert fiat prices to cryptocurrency using current exchange rates - Generate QR codes that include both address and exact amount - Display both fiat and crypto amounts for clarity - Provide separate copy buttons for address and amount - **Verify the converted amount is above the minimum transaction threshold** - **Always display minimum transaction amount prominently** - even for fixed amounts, users need to know the minimum **Minimum Transaction Amount Handling:** - **Always fetch and display minimum amounts** from the info endpoint for your chosen cryptocurrency - **Always show minimum requirements in your UI** - this is not optional, it prevents fund loss - **Validate amounts before accepting payments** - if a user's payment would be below minimum, show an error - **Display clear warnings** about minimum requirements in your UI - **For ecommerce payments**, ensure your fiat price converts to an amount above the minimum > **WARNING** >**Critical: Minimum Transaction Amounts** > > **Why this matters:** Every cryptocurrency has minimum transaction amounts that vary by blockchain. Transactions below these minimums are **ignored by BlockBee systems and funds will be permanently lost**. > > **What you must do:** > 1. **Always fetch minimum amounts** using the info endpoint for your chosen cryptocurrency > 2. **Display minimum requirements prominently** in your payment UI > 3. **Validate amounts before accepting payments** - show errors for amounts below minimum > > **Example validation:** > ```javascript > // Check if amount meets minimum requirements > const tickerInfo = await getTickerInfo(); > const minimumAmount = tickerInfo.minimum_transaction_coin; > > if (paymentAmount < minimumAmount) { > showError(`Amount too low. Minimum required: ${minimumAmount} ${ticker.toUpperCase()}`); > return; > } > ``` > > **For ecommerce:** Ensure your fiat prices convert to amounts above the minimum. Consider adjusting prices or adding minimum order amounts if necessary. > **INFO** >**QR Code Value Parameter:** The `value` parameter in QR codes may not be compatible with all wallets and exchanges. Some may ignore the amount field, so always display the amount separately as well. > **INFO** >**Template Updates Required:** For the Ruby, C#, Java, Go, and Bash examples above, you'll need to update your payment templates to include the minimum amount display. Add this line to your templates: > > ```html >

⚠️ Minimum transaction amount: [MINIMUM_AMOUNT] [CURRENCY]

> ``` > > Replace `[MINIMUM_AMOUNT]` and `[CURRENCY]` with the appropriate template variables for your framework. > **WARNING** >**Real-time User Notifications:** You should implement an automated way to notify users when their payment/deposit has been received. When BlockBee sends a webhook notification, update your UI immediately to show the payment status change. Consider using: > > - **WebSockets** - For real-time browser updates > - **Server-Sent Events (SSE)** - For live status updates > - **Email notifications** - For payment confirmations > - **Push notifications** - For mobile apps > - **Database polling** - As a fallback method > > Users expect immediate feedback when they send a payment, so don't rely on manual page refreshes! ## Step 3: Track Payments You can track payments using two methods: **Webhooks** (recommended) or **Logs Endpoint** (polling). Most applications use webhooks for real-time updates, but the logs endpoint is useful for troubleshooting or as a backup method. ### Method 1: Webhooks (Recommended) Set up webhook endpoints to receive real-time notifications when payments are received. BlockBee sends two types of webhooks: #### Webhook Types: - **Pending** - Payment detected in mempool but not yet confirmed (particularly useful for slower blockchains like Bitcoin, Litecoin, and Bitcoin Cash) - **Confirmed** - Payment has received the number of confirmations specified in the `confirmations` parameter when creating the payment address For complete webhook field documentation, see **[Custom Payment Flow Webhooks](/webhooks/custom-payment-flow-webhooks)** - includes all fields, examples, and implementation details. > **INFO: Tracking Payments with Custom Parameters** >When creating custom payment flow payments, always add your own query parameters to the `callback` URL to track which order or user the payment belongs to. For example, adding `?order_id=123` or `?user_id=456` will ensure that these parameters are included in the webhook payload. > > This is the recommended way to link a BlockBee payment to a specific order or user in your system. > **INFO** >**Webhook Method & Format:** You control how webhooks are delivered when creating the payment: > > - **Default:** GET requests with URL-encoded parameters > - **`post=1`:** POST requests with data in request body > - **`json=1`:** JSON format (works with both GET and POST) > - **`post=1&json=1`:** POST requests with JSON data in body > > Your webhook endpoint should handle the format you choose during payment creation. #### Webhook Implementation: ```javascript // Express.js webhook handler - handles both GET and POST app.all('/webhook', express.json(), (req, res) => { // Handle both GET (default) and POST (if post=1 was set) const webhookData = req.method === 'GET' ? req.query : req.body; const { uuid, address_in, address_out, txid_in, txid_out, confirmations, value_coin, value_coin_convert, value_forwarded_coin, value_forwarded_coin_convert, fee_coin, coin, price, pending, order_id, user_id } = webhookData; // Note: order_id and user_id come from custom parameters you added to callback URL // Check if we've already processed this transaction const alreadyProcessed = checkTransactionInDatabase(uuid); if (!alreadyProcessed) { if (pending === 1) { // Payment detected but not confirmed console.log(`Pending payment for ${order_id || user_id}: ${value_coin} ${coin.toUpperCase()} to ${address_in}`); console.log(`UUID: ${uuid}, Price: $${price}`); // Store transaction in database with UUID storeTransaction({ uuid: uuid, address_in: address_in, address_out: address_out, txid_in: txid_in, amount: value_coin, coin: coin, price: price, status: 'pending', value_coin_convert: value_coin_convert, processed_at: new Date() }); // Notify user (WebSocket, email, etc.) notifyUser(address_in, 'pending', { uuid: uuid, amount: value_coin, coin: coin, usd_value: value_coin_convert ? JSON.parse(value_coin_convert).USD : null }); } else if (pending === 0) { // Payment confirmed console.log(`Confirmed payment for ${order_id || user_id}: ${value_coin} ${coin.toUpperCase()} to ${address_in}`); console.log(`UUID: ${uuid}, Forwarded: ${value_forwarded_coin}, Fee: ${fee_coin}`); // Update database updateTransaction(uuid, { txid_out: txid_out, confirmations: confirmations, value_forwarded_coin: value_forwarded_coin, value_forwarded_coin_convert: value_forwarded_coin_convert, fee_coin: fee_coin, status: 'confirmed', confirmed_at: new Date() }); // Process order, send confirmation email, etc. processSuccessfulPayment(uuid, { orderId: order_id, userId: user_id, amount: value_coin, forwarded_amount: value_forwarded_coin, fee: fee_coin, coin: coin, confirmations: confirmations }); // Notify user notifyUser(address_in, 'confirmed', { uuid: uuid, amount: value_coin, forwarded_amount: value_forwarded_coin, coin: coin, confirmations: confirmations }); } } else { console.log(`Duplicate webhook received for UUID: ${uuid}`); } // Always respond with *ok* or HTTP 200 to stop retries res.status(200).send('*ok*'); }); ``` ```php $txid_in, 'amount' => $value_coin, 'confirmations' => $confirmations ]); // Notify user notifyUser($address_in, 'pending'); } elseif ($confirmations >= 1) { // Payment confirmed $identifier = $order_id ?? $user_id ?? 'unknown'; error_log("Confirmed payment for {$identifier}: {$value_coin} BTC to {$address_in}"); // Update database updatePaymentStatus($address_in, 'confirmed', [ 'txid' => $txid_in, 'amount' => $value_coin, 'confirmations' => $confirmations ]); // Process order processSuccessfulPayment($address_in); // Notify user notifyUser($address_in, 'confirmed'); } http_response_code(200); echo 'OK'; function updatePaymentStatus($address, $status, $data) { // Update your database here // Example: UPDATE payments SET status = ?, data = ? WHERE address = ? } function processSuccessfulPayment($address) { // Process the successful payment // Send confirmation email, fulfill order, etc. } function notifyUser($address, $status) { // Notify user via WebSocket, email, etc. } ?> ``` ```python # Flask webhook handler from flask import Flask, request, jsonify import json @app.route('/webhook', methods=['POST']) def webhook(): data = request.get_json() address_in = data['address_in'] address_out = data['address_out'] txid_in = data['txid_in'] txid_out = data['txid_out'] confirmations = data['confirmations'] value = data['value'] value_coin = data['value_coin'] pending = data['pending'] order_id = data.get('order_id') # This comes from custom parameter you added to callback URL user_id = data.get('user_id') # This comes from custom parameter you added to callback URL if pending == 1: # Payment detected but not confirmed identifier = order_id or user_id or 'unknown' print(f"Pending payment for {identifier}: {value_coin} BTC to {address_in}") // Update database update_payment_status(address_in, 'pending', { 'txid': txid_in, 'amount': value_coin, 'confirmations': confirmations }) // Notify user notify_user(address_in, 'pending') elif confirmations >= 1: # Payment confirmed identifier = order_id or user_id or 'unknown' print(f"Confirmed payment for {identifier}: {value_coin} BTC to {address_in}") // Update database update_payment_status(address_in, 'confirmed', { 'txid': txid_in, 'amount': value_coin, 'confirmations': confirmations }) // Process order process_successful_payment(address_in) // Notify user notify_user(address_in, 'confirmed') return jsonify({'status': 'ok'}), 200 def update_payment_status(address, status, data): // Update your database here pass def process_successful_payment(address): // Process the successful payment pass def notify_user(address, status): // Notify user via WebSocket, email, etc. pass ``` ```ruby # Sinatra webhook handler post '/webhook' do data = JSON.parse(request.body.read) address_in = data['address_in'] address_out = data['address_out'] txid_in = data['txid_in'] txid_out = data['txid_out'] confirmations = data['confirmations'] value = data['value'] value_coin = data['value_coin'] pending = data['pending'] if pending == 1 // Payment detected but not confirmed puts "Pending payment: #{value_coin} BTC to #{address_in}" // Update database update_payment_status(address_in, 'pending', { txid: txid_in, amount: value_coin, confirmations: confirmations }) // Notify user notify_user(address_in, 'pending') elsif confirmations >= 1 // Payment confirmed puts "Confirmed payment: #{value_coin} BTC to #{address_in}" // Update database update_payment_status(address_in, 'confirmed', { txid: txid_in, amount: value_coin, confirmations: confirmations }) // Process order process_successful_payment(address_in) // Notify user notify_user(address_in, 'confirmed') end status 200 'OK' end ``` ```csharp // ASP.NET Core webhook handler [HttpPost("webhook")] public IActionResult Webhook([FromBody] WebhookData data) { if (data.Pending == 1) { // Payment detected but not confirmed _logger.LogInformation($"Pending payment: {data.ValueCoin} BTC to {data.AddressIn}"); // Update database UpdatePaymentStatus(data.AddressIn, "pending", new { Txid = data.TxidIn, Amount = data.ValueCoin, Confirmations = data.Confirmations }); // Notify user NotifyUser(data.AddressIn, "pending"); } else if (data.Confirmations >= 1) { // Payment confirmed _logger.LogInformation($"Confirmed payment: {data.ValueCoin} BTC to {data.AddressIn}"); // Update database UpdatePaymentStatus(data.AddressIn, "confirmed", new { Txid = data.TxidIn, Amount = data.ValueCoin, Confirmations = data.Confirmations }); // Process order ProcessSuccessfulPayment(data.AddressIn); // Notify user NotifyUser(data.AddressIn, "confirmed"); } return Ok("OK"); } public class WebhookData { public string AddressIn { get; set; } public string AddressOut { get; set; } public string TxidIn { get; set; } public string TxidOut { get; set; } public int Confirmations { get; set; } public decimal Value { get; set; } public decimal ValueCoin { get; set; } public int Pending { get; set; } } ``` ```java // Spring Boot webhook handler @PostMapping("/webhook") public ResponseEntity webhook(@RequestBody WebhookData data) { if (data.getPending() == 1) { // Payment detected but not confirmed logger.info("Pending payment: {} BTC to {}", data.getValueCoin(), data.getAddressIn()); // Update database updatePaymentStatus(data.getAddressIn(), "pending", Map.of("txid", data.getTxidIn(), "amount", data.getValueCoin(), "confirmations", data.getConfirmations())); // Notify user notifyUser(data.getAddressIn(), "pending"); } else if (data.getConfirmations() >= 1) { // Payment confirmed logger.info("Confirmed payment: {} BTC to {}", data.getValueCoin(), data.getAddressIn()); // Update database updatePaymentStatus(data.getAddressIn(), "confirmed", Map.of("txid", data.getTxidIn(), "amount", data.getValueCoin(), "confirmations", data.getConfirmations())); // Process order processSuccessfulPayment(data.getAddressIn()); // Notify user notifyUser(data.getAddressIn(), "confirmed"); } return ResponseEntity.ok("OK"); } ``` ```go // Go webhook handler func webhookHandler(w http.ResponseWriter, r *http.Request) { var data WebhookData body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } if err := json.Unmarshal(body, &data); err != nil { http.Error(w, "Bad request", http.StatusBadRequest) return } if data.Pending == 1 { // Payment detected but not confirmed log.Printf("Pending payment: %s BTC to %s", data.ValueCoin, data.AddressIn) // Update database updatePaymentStatus(data.AddressIn, "pending", map[string]interface{}{ "txid": data.TxidIn, "amount": data.ValueCoin, "confirmations": data.Confirmations, }) // Notify user notifyUser(data.AddressIn, "pending") } else if data.Confirmations >= 1 { // Payment confirmed log.Printf("Confirmed payment: %s BTC to %s", data.ValueCoin, data.AddressIn) // Update database updatePaymentStatus(data.AddressIn, "confirmed", map[string]interface{}{ "txid": data.TxidIn, "amount": data.ValueCoin, "confirmations": data.Confirmations, }) // Process order processSuccessfulPayment(data.AddressIn) // Notify user notifyUser(data.AddressIn, "confirmed") } w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) } type WebhookData struct { AddressIn string `json:"address_in"` AddressOut string `json:"address_out"` TxidIn string `json:"txid_in"` TxidOut string `json:"txid_out"` Confirmations int `json:"confirmations"` Value float64 `json:"value"` ValueCoin string `json:"value_coin"` Pending int `json:"pending"` } ``` ```bash # Test webhook locally with ngrok ngrok http 3000 # Your webhook URL will be: https://abc123.ngrok.io/webhook ``` > **INFO** >**Webhook Reliability & Security:** > > **Response Requirements:** > - Always respond with `*ok*` message or HTTP 200 status code to stop retries > - BlockBee uses exponential backoff: starts at 6 minutes, doubles each retry, stops after 3 days > > **IP Whitelisting:** > - Whitelist these BlockBee server IPs: `51.77.105.132` and `135.125.112.47` > - This prevents security systems (like Cloudflare) from blocking webhooks > > **Common Issues:** > - Wrong token sent to address (e.g., created USDT address but sent TRX) > - Callback URL not reachable online > - Security systems blocking webhook requests > > **Duplicate Prevention:** > - Always check the `uuid` field to prevent processing the same transaction twice > - Store UUIDs in your database before processing any payment ### Method 2: Logs Endpoint (Polling) If webhooks aren't suitable for your setup, or as a backup method, you can use the logs endpoint to check payment status. This is particularly useful for troubleshooting webhook issues or implementing cron jobs to monitor payments. > **INFO: Using Our Official Libraries** >You can also check payment logs using our official libraries: > > ```javascript > // Using the official Node.js library > const BlockBee = require('@blockbee/api'); > > const bb = new BlockBee('btc', 'YOUR_WALLET_ADDRESS', callbackUrl, params, blockbeeParams, 'YOUR_API_KEY'); > const logs = await bb.checkLogs(); > // logs.callbacks contains the payment history > ``` > > ```php > // Using the official PHP library > $bb = new BlockBee\BlockBee('btc', 'YOUR_WALLET_ADDRESS', $callbackUrl, $params, $blockbeeParams, 'YOUR_API_KEY'); > $logs = $bb->check_logs(); > // $logs->callbacks contains the payment history > ``` > > ```python > # Using the official Python library > from blockbee import BlockBeeHelper > > bb = BlockBeeHelper('btc', 'YOUR_WALLET_ADDRESS', callback_url, params, bb_params, 'YOUR_API_KEY') > logs = bb.get_logs() > # logs['callbacks'] contains the payment history > ``` ```javascript // Check payment status using logs endpoint const checkPaymentStatus = async (callbackUrl) => { const encodedCallback = encodeURIComponent(callbackUrl); const params = new URLSearchParams({ apikey: 'YOUR_API_KEY', callback: encodedCallback }); const response = await fetch(`https://api.blockbee.io/btc/logs/?${params}`); const data = await response.json(); // Check if API request was successful if (data.status !== 'success') { console.error('API request failed:', data); return { error: 'API request failed' }; } if (data.callbacks && data.callbacks.length > 0) { const processedTransactions = []; // Process each transaction for (const callback of data.callbacks) { // Check if transaction is confirmed (pending = 0 in logs) const confirmedLog = callback.logs.find(log => log.pending === 0); const isConfirmed = confirmedLog !== undefined; processedTransactions.push({ uuid: callback.uuid || `${callback.txid_in}-${callback.value_coin}`, // Use UUID or fallback txid: callback.txid_in, amount: callback.value_coin, confirmations: callback.confirmations, status: callback.result, isConfirmed: isConfirmed, lastUpdate: callback.last_update }); } return { transactions: processedTransactions }; } return { transactions: [] }; }; // Process payments with duplicate prevention const processPayments = async (callbackUrl) => { const result = await checkPaymentStatus(callbackUrl); if (result.error) { console.error('Failed to check payment status:', result.error); return; } for (const transaction of result.transactions) { // Check if we've already processed this transaction UUID const alreadyProcessed = await checkTransactionInDatabase(transaction.uuid); if (!alreadyProcessed) { if (transaction.isConfirmed && transaction.status === 'done') { console.log(`New confirmed payment: ${transaction.amount} BTC`); // Store transaction in database with UUID await storeTransaction({ uuid: transaction.uuid, txid: transaction.txid, amount: transaction.amount, confirmations: transaction.confirmations, status: 'confirmed', processed_at: new Date() }); // Process the payment (fulfill order, send confirmation, etc.) await processConfirmedPayment(transaction); } else if (transaction.status === 'pending') { console.log(`New pending payment: ${transaction.amount} BTC`); // Store as pending transaction await storeTransaction({ uuid: transaction.uuid, txid: transaction.txid, amount: transaction.amount, confirmations: transaction.confirmations, status: 'pending', processed_at: new Date() }); // Notify user of pending payment await notifyPendingPayment(transaction); } } } }; // Helper functions (implement based on your database) const checkTransactionInDatabase = async (uuid) => { // Check if transaction UUID exists in your database // Return true if exists, false if new }; const storeTransaction = async (transactionData) => { // Store transaction in your database // Include the UUID to prevent duplicate processing }; const processConfirmedPayment = async (transaction) => { // Process confirmed payment (fulfill order, send email, etc.) }; const notifyPendingPayment = async (transaction) => { // Notify user of pending payment }; ``` ```php 'YOUR_API_KEY', 'callback' => $encodedCallback ]); $url = 'https://api.blockbee.io/btc/logs/?' . $params; $result = file_get_contents($url); $data = json_decode($result, true); // Check if API request was successful if ($data['status'] !== 'success') { error_log('API request failed: ' . json_encode($data)); return ['error' => 'API request failed']; } if (!empty($data['callbacks'])) { $processedTransactions = []; // Process each transaction foreach ($data['callbacks'] as $callback) { // Check if transaction is confirmed (pending = 0 in logs) $isConfirmed = false; foreach ($callback['logs'] as $log) { if ($log['pending'] == 0) { $isConfirmed = true; break; } } $processedTransactions[] = [ 'uuid' => $callback['uuid'] ?? $callback['txid_in'] . '-' . $callback['value_coin'], 'txid' => $callback['txid_in'], 'amount' => $callback['value_coin'], 'confirmations' => $callback['confirmations'], 'status' => $callback['result'], 'isConfirmed' => $isConfirmed, 'lastUpdate' => $callback['last_update'] ]; } return ['transactions' => $processedTransactions]; } return ['transactions' => []]; } // Process payments with duplicate prevention function processPayments($callbackUrl) { $result = checkPaymentStatus($callbackUrl); if (isset($result['error'])) { error_log('Failed to check payment status: ' . $result['error']); return; } foreach ($result['transactions'] as $transaction) { // Check if we've already processed this transaction UUID $alreadyProcessed = checkTransactionInDatabase($transaction['uuid']); if (!$alreadyProcessed) { if ($transaction['isConfirmed'] && $transaction['status'] === 'done') { echo "New confirmed payment: " . $transaction['amount'] . " BTC\n"; // Store transaction in database with UUID storeTransaction([ 'uuid' => $transaction['uuid'], 'txid' => $transaction['txid'], 'amount' => $transaction['amount'], 'confirmations' => $transaction['confirmations'], 'status' => 'confirmed', 'processed_at' => date('Y-m-d H:i:s') ]); // Process the payment (fulfill order, send confirmation, etc.) processConfirmedPayment($transaction); } elseif ($transaction['status'] === 'pending') { echo "New pending payment: " . $transaction['amount'] . " BTC\n"; // Store as pending transaction storeTransaction([ 'uuid' => $transaction['uuid'], 'txid' => $transaction['txid'], 'amount' => $transaction['amount'], 'confirmations' => $transaction['confirmations'], 'status' => 'pending', 'processed_at' => date('Y-m-d H:i:s') ]); // Notify user of pending payment notifyPendingPayment($transaction); } } } } // Helper functions (implement based on your database) function checkTransactionInDatabase($uuid) { // Check if transaction UUID exists in your database // Return true if exists, false if new } function storeTransaction($transactionData) { // Store transaction in your database // Include the UUID to prevent duplicate processing } function processConfirmedPayment($transaction) { // Process confirmed payment (fulfill order, send email, etc.) } function notifyPendingPayment($transaction) { // Notify user of pending payment } ?> ``` ```python import requests from urllib.parse import quote def check_payment_status(callback_url): """Check payment status using logs endpoint""" encoded_callback = quote(callback_url) params = { 'apikey': 'YOUR_API_KEY', 'callback': encoded_callback } response = requests.get('https://api.blockbee.io/btc/logs/', params=params) data = response.json() if data.get('callbacks'): latest_callback = data['callbacks'][0] return { 'status': latest_callback['result'], 'confirmations': latest_callback['confirmations'], 'amount': latest_callback['value_coin'], 'txid': latest_callback['txid_in'] } return {'status': 'no_payments', 'confirmations': 0, 'amount': 0} def poll_payment_status(callback_url): """Use in a cron job or polling function""" status = check_payment_status(callback_url) if status['status'] == 'done': print("Payment confirmed and processed!") # Update database, notify user, etc. elif status['status'] == 'pending': print("Payment detected but not confirmed yet") # Show pending status to user ``` ```ruby require 'net/http' require 'json' require 'uri' def check_payment_status(callback_url) encoded_callback = URI.encode_www_form_component(callback_url) params = { apikey: 'YOUR_API_KEY', callback: encoded_callback } query_string = URI.encode_www_form(params) uri = URI("https://api.blockbee.io/btc/logs/?#{query_string}") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new(uri) response = http.request(request) data = JSON.parse(response.body) if data['callbacks'] && !data['callbacks'].empty? latest_callback = data['callbacks'][0] return { status: latest_callback['result'], confirmations: latest_callback['confirmations'], amount: latest_callback['value_coin'], txid: latest_callback['txid_in'] } end { status: 'no_payments', confirmations: 0, amount: 0 } end def poll_payment_status(callback_url) status = check_payment_status(callback_url) case status[:status] when 'done' puts "Payment confirmed and processed!" # Update database, notify user, etc. when 'pending' puts "Payment detected but not confirmed yet" # Show pending status to user end end ``` ```csharp using System; using System.Net.Http; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; public class PaymentStatusChecker { private static readonly HttpClient client = new HttpClient(); public async Task CheckPaymentStatus(string callbackUrl) { var encodedCallback = HttpUtility.UrlEncode(callbackUrl); var queryParams = HttpUtility.ParseQueryString(string.Empty); queryParams["apikey"] = "YOUR_API_KEY"; queryParams["callback"] = encodedCallback; var url = $"https://api.blockbee.io/btc/logs/?{queryParams}"; var response = await client.GetAsync(url); var result = await response.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject(result); if (data.callbacks != null && data.callbacks.Count > 0) { var latestCallback = data.callbacks[0]; return new { status = latestCallback.result, confirmations = latestCallback.confirmations, amount = latestCallback.value_coin, txid = latestCallback.txid_in }; } return new { status = "no_payments", confirmations = 0, amount = 0 }; } public async Task PollPaymentStatus(string callbackUrl) { var status = await CheckPaymentStatus(callbackUrl); if (status.status == "done") { Console.WriteLine("Payment confirmed and processed!"); // Update database, notify user, etc. } else if (status.status == "pending") { Console.WriteLine("Payment detected but not confirmed yet"); // Show pending status to user } } } ``` ```java import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import com.google.gson.Gson; import java.util.Map; public class PaymentStatusChecker { private static final HttpClient client = HttpClient.newHttpClient(); private static final Gson gson = new Gson(); public Map checkPaymentStatus(String callbackUrl) { try { String encodedCallback = URLEncoder.encode(callbackUrl, StandardCharsets.UTF_8); String url = "https://api.blockbee.io/btc/logs/" + "?apikey=YOUR_API_KEY" + "&callback=" + encodedCallback; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); Map data = gson.fromJson(response.body(), Map.class); if (data.containsKey("callbacks") && !((java.util.List) data.get("callbacks")).isEmpty()) { Map latestCallback = (Map) ((java.util.List) data.get("callbacks")).get(0); return Map.of( "status", latestCallback.get("result"), "confirmations", latestCallback.get("confirmations"), "amount", latestCallback.get("value_coin"), "txid", latestCallback.get("txid_in") ); } return Map.of("status", "no_payments", "confirmations", 0, "amount", 0); } catch (Exception e) { throw new RuntimeException("Payment status check failed", e); } } public void pollPaymentStatus(String callbackUrl) { Map status = checkPaymentStatus(callbackUrl); if ("done".equals(status.get("status"))) { System.out.println("Payment confirmed and processed!"); // Update database, notify user, etc. } else if ("pending".equals(status.get("status"))) { System.out.println("Payment detected but not confirmed yet"); // Show pending status to user } } } ``` ```go package main import ( "encoding/json" "fmt" "net/http" "net/url" "io/ioutil" ) type PaymentStatus struct { Status string `json:"status"` Confirmations int `json:"confirmations"` Amount float64 `json:"amount"` Txid string `json:"txid"` } func checkPaymentStatus(callbackURL string) (*PaymentStatus, error) { encodedCallback := url.QueryEscape(callbackURL) apiURL := fmt.Sprintf("https://api.blockbee.io/btc/logs/?apikey=YOUR_API_KEY&callback=%s", encodedCallback) resp, err := http.Get(apiURL) if err != nil { return nil, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } var data map[string]interface{} json.Unmarshal(body, &data) if callbacks, ok := data["callbacks"].([]interface{}); ok && len(callbacks) > 0 { latestCallback := callbacks[0].(map[string]interface{}) return &PaymentStatus{ Status: latestCallback["result"].(string), Confirmations: int(latestCallback["confirmations"].(float64)), Amount: latestCallback["value_coin"].(float64), Txid: latestCallback["txid_in"].(string), }, nil } return &PaymentStatus{Status: "no_payments", Confirmations: 0, Amount: 0}, nil } func pollPaymentStatus(callbackURL string) { status, err := checkPaymentStatus(callbackURL) if err != nil { fmt.Printf("Error checking payment status: %v\n", err) return } switch status.Status { case "done": fmt.Println("Payment confirmed and processed!") // Update database, notify user, etc. case "pending": fmt.Println("Payment detected but not confirmed yet") // Show pending status to user } } ``` ```bash # Check payment status using logs endpoint CALLBACK_URL=$(echo "https://yoursite.com/webhook?order_id=12345" | jq -rR @uri) curl -G "https://api.blockbee.io/btc/logs/" \ --data-urlencode "apikey=YOUR_API_KEY" \ --data-urlencode "callback=${CALLBACK_URL}" \ | jq '.callbacks[0] | {status: .result, confirmations: .confirmations, amount: .value_coin, txid: .txid_in}' # Use in a cron job script #!/bin/bash check_payment_status() { local callback_url="$1" local encoded_callback=$(echo "$callback_url" | jq -rR @uri) curl -s -G "https://api.blockbee.io/btc/logs/" \ --data-urlencode "apikey=YOUR_API_KEY" \ --data-urlencode "callback=${encoded_callback}" \ | jq -r '.callbacks[0].result // "no_payments"' } # Poll payment status status=$(check_payment_status "https://yoursite.com/webhook?order_id=12345") if [ "$status" = "done" ]; then echo "Payment confirmed and processed!" elif [ "$status" = "pending" ]; then echo "Payment detected but not confirmed yet" fi ``` ### API Reference - **[GET /{ticker}/logs/](../api/tickerlogs)** - Check payment address logs and webhook history ### Important Implementation Notes **API Status vs Payment Status:** - **`status`** field indicates API request success (`success`/`error`) - **Payment status** is found within the `callbacks` array and `logs` entries **Multiple Transactions:** - Each payment address can receive multiple transactions - Each transaction has a unique `uuid` (or use `txid_in` + `value_coin` as fallback) - **Always store the UUID** in your database to prevent duplicate processing **Confirmation Detection:** - Check `logs` array within each callback - Transaction is confirmed when `pending = 0` in any log entry - Don't rely solely on `result` field - check the logs for confirmation status **Duplicate Prevention:** - Store transaction UUIDs in your database - Check if UUID exists before processing any transaction - This prevents processing the same transaction multiple times ### Payment Status Values The logs endpoint returns different `result` values indicating payment status: - **`pending`** - Transaction is being confirmed by the blockchain - **`sent`** - Payment forwarded to your address but webhook didn't receive valid `*ok*` response - **`done`** - Payment forwarded and webhook sent to your URL with valid `*ok*` response received ### When to Use Each Method **Use Webhooks when:** - You need real-time payment notifications - Your application can receive HTTP requests - You want the most efficient solution **Use Logs Endpoint when:** - Webhooks aren't feasible (firewall restrictions, etc.) - You need to troubleshoot webhook issues - Implementing backup payment monitoring - Running periodic cron jobs to check payment status > **INFO** >**Pro Tip:** Use webhooks as your primary method and logs endpoint as a backup. This ensures you never miss a payment even if webhook delivery fails. ## Testing Your Integration Test your payment flow using real cryptocurrency with minimal cost: ### 1. Use Litecoin for Testing We recommend using **Litecoin (LTC)** for testing because: - Low transaction fees (typically under $0.01) - Fast confirmation times (2.5 minutes average) - Low BlockBee fees on small amounts - Real blockchain testing without high costs Simply change your ticker from `btc` to `ltc` in your existing code. **If your integration works with LTC, it will work with any ticker** (`btc`, `eth`, `trc20/usdt`, `bep20/usdc`, etc.) - the API endpoints and webhook structure are identical across all cryptocurrencies. > **TIP** >**Multiple Payment Options:** Since the API is universal across all cryptocurrencies, you can easily offer your customers multiple payment options. Just let them choose their preferred cryptocurrency and use the corresponding ticker in your API calls. The same code handles Bitcoin, Ethereum, USDT (eg `trc20/usdt`), and any other supported cryptocurrency. > > Check the full list of supported cryptocurrencies at [blockbee.io/cryptocurrencies](https://blockbee.io/cryptocurrencies). ### 2. Test with $2 Worth of LTC - Send approximately **$2 USD worth of Litecoin** to test the complete flow - This covers blockchain fees + BlockBee fees with minimal cost - You can buy small amounts of LTC on most exchanges - Test both pending and confirmed webhook states ### 3. Test Checklist - ✅ Payment creation works with LTC - ✅ QR code displays correctly - ✅ Address copying functions - ✅ Pending webhook received (fast with LTC) - ✅ Confirmed webhook received - ✅ UI updates correctly - ✅ Amount calculations are accurate - ✅ Success flow completes ### 4. Testing Environment Setup ```bash # Use ngrok for local webhook testing ngrok http 3000 # Your test webhook URL: https://abc123.ngrok.io/webhook ``` > **TIP** >**Why Litecoin?** With $2 worth of LTC, total fees (blockchain + BlockBee) are typically under $0.10, making it the most cost-effective way to test real payment flows. Once testing is complete, switch to your preferred cryptocurrency for production. > **SUCCESS** >**Ready for production?** Once testing is complete, update your ticker from `ltc` to your preferred cryptocurrency (e.g., `btc`, `eth`, `trc20/usdt`) and update your destination addresses.