## Overview Checkout Payments is the easiest way to start accepting cryptocurrency. With this method, BlockBee handles the entire payment interface, multi-currency selection, and real-time status updates on a secure, hosted page. Your only responsibility is to handle the final payment notification (webhook) to confirm and process the order on your end. This makes it the perfect solution if you want a fast, secure, and simple integration without building a custom UI. > **INFO** >**Estimated time:** 5-10 minutes for a basic implementation ## Setup Before you can start accepting payments, 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. Configure Payment Settings 1. **Set Default Currency:** Go to [Payment Settings](https://dash.blockbee.io/profile/checkout) and configure your default FIAT currency (USD, EUR, GBP, etc.) 2. **Customize Checkout Page:** Optionally customize the appearance and branding of your checkout pages ### 4. 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. ## Step 1: Create Payment Request First, make a request to our Checkout API. This will generate a unique payment URL for your customer. ### API Endpoint **Method:** `GET` **URL:** `https://api.blockbee.io/checkout/request/` > **INFO: Tracking Orders with Custom Parameters** >You can add your own query parameters to `notify_url` and `redirect_url` to track orders. For example, adding `?order_id=123` will ensure that `order_id=123` is included in the webhook payload and the final redirect URL. This is the recommended way to link a BlockBee payment to a specific order in your system. > > **Important:** Always include a unique identifier (like `order_id`, `user_id`, or `transaction_id`) in your `notify_url` to properly track which order the webhook belongs to. This identifier will be returned in the webhook payload as GET parameters, allowing you to match the payment to the correct order in your database. > > **Note:** The `payment_id` returned in the API response can also be used as a unique identifier for tracking payments, in addition to any custom parameters you add to the URLs. > **TIP** >**API Reference:** For complete parameter documentation, see our [Checkout Request API Reference](/api/checkoutrequest). ### Code Examples ```javascript // Create a checkout payment const createCheckoutPayment = async (orderId, amount) => { const params = new URLSearchParams({ apikey: 'YOUR_API_KEY', redirect_url: `https://yoursite.com/success/?order_id=${orderId}`, value: amount.toString(), currency: 'usd', item_description: `Order #${orderId}`, notify_url: `https://yoursite.com/webhook/?order_id=${orderId}`, post: '1' }); const response = await fetch(`https://api.blockbee.io/checkout/request/?${params}`); const result = await response.json(); if (result.status === 'success') { return { paymentId: result.payment_id, paymentUrl: result.payment_url, successToken: result.success_token }; } else { throw new Error('Failed to create payment: ' + result.message); } }; ``` ```php 'YOUR_API_KEY', 'redirect_url' => 'https://yoursite.com/success/?order_id=' . $orderId, 'value' => $amount, 'currency' => 'usd', 'item_description' => 'Order #' . $orderId, 'notify_url' => 'https://yoursite.com/webhook/?order_id=' . $orderId, 'post' => '1' ]; $url = 'https://api.blockbee.io/checkout/request/?' . http_build_query($params); $response = file_get_contents($url); $result = json_decode($response, true); if ($result['status'] === 'success') { return [ 'paymentId' => $result['payment_id'], 'paymentUrl' => $result['payment_url'], 'successToken' => $result['success_token'] ]; } else { throw new Exception('Failed to create payment: ' . $result['message']); } } ?> ``` ```python import requests def create_checkout_payment(order_id, amount): params = { 'apikey': 'YOUR_API_KEY', 'redirect_url': f'https://yoursite.com/success/?order_id={order_id}', 'value': str(amount), 'currency': 'usd', 'item_description': f'Order #{order_id}', 'notify_url': f'https://yoursite.com/webhook/?order_id={order_id}', 'post': '1' } response = requests.get('https://api.blockbee.io/checkout/request/', params=params) result = response.json() if result['status'] == 'success': return { 'paymentId': result['payment_id'], 'paymentUrl': result['payment_url'], 'successToken': result['success_token'] } else: raise Exception(f'Failed to create payment: {result.get("message", "Unknown error")}') ``` ```ruby require 'net/http' require 'json' require 'uri' def create_checkout_payment(order_id, amount) params = { 'apikey' => 'YOUR_API_KEY', 'redirect_url' => "https://yoursite.com/success/?order_id=#{order_id}", 'value' => amount.to_s, 'currency' => 'usd', 'item_description' => "Order ##{order_id}", 'notify_url' => "https://yoursite.com/webhook/?order_id=#{order_id}", 'post' => '1' } uri = URI('https://api.blockbee.io/checkout/request/') uri.query = URI.encode_www_form(params) response = Net::HTTP.get_response(uri) result = JSON.parse(response.body) if result['status'] == 'success' { paymentId: result['payment_id'], paymentUrl: result['payment_url'], successToken: result['success_token'] } else raise "Failed to create payment: #{result['message']}" end end ``` ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; public class PaymentService { private static readonly HttpClient client = new HttpClient(); public async Task CreateCheckoutPayment(string orderId, decimal amount) { var parameters = new Dictionary { ["apikey"] = "YOUR_API_KEY", ["redirect_url"] = $"https://yoursite.com/success/?order_id={orderId}", ["value"] = amount.ToString(), ["currency"] = "usd", ["item_description"] = $"Order #{orderId}", ["notify_url"] = $"https://yoursite.com/webhook/?order_id={orderId}", ["post"] = "1" }; var queryString = string.Join("&", parameters.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}")); var url = $"https://api.blockbee.io/checkout/request/?{queryString}"; var response = await client.GetStringAsync(url); var result = JsonConvert.DeserializeObject>(response); if (result["status"].ToString() == "success") { return new PaymentResult { PaymentId = result["payment_id"].ToString(), PaymentUrl = result["payment_url"].ToString(), SuccessToken = result["success_token"].ToString() }; } else { throw new Exception($"Failed to create payment: {result.GetValueOrDefault("message", "Unknown error")}"); } } } public class PaymentResult { public string PaymentId { get; set; } public string PaymentUrl { get; set; } public string SuccessToken { get; set; } } ``` ```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 java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; public class CheckoutPayment { private static final HttpClient client = HttpClient.newHttpClient(); private static final ObjectMapper mapper = new ObjectMapper(); public PaymentResult createCheckoutPayment(String orderId, double amount) throws Exception { String baseUrl = "https://api.blockbee.io/checkout/request/"; String queryParams = String.format( "apikey=%s&redirect_url=%s&value=%s¤cy=usd&item_description=%s¬ify_url=%s&post=1", "YOUR_API_KEY", URLEncoder.encode("https://yoursite.com/success/?order_id=" + orderId, StandardCharsets.UTF_8), amount, URLEncoder.encode("Order #" + orderId, StandardCharsets.UTF_8), URLEncoder.encode("https://yoursite.com/webhook/?order_id=" + orderId, StandardCharsets.UTF_8) ); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "?" + queryParams)) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); Map result = mapper.readValue(response.body(), Map.class); if ("success".equals(result.get("status"))) { return new PaymentResult( (String) result.get("payment_id"), (String) result.get("payment_url"), (String) result.get("success_token") ); } else { throw new Exception("Failed to create payment: " + result.getOrDefault("message", "Unknown error")); } } } class PaymentResult { private String paymentId; private String paymentUrl; private String successToken; public PaymentResult(String paymentId, String paymentUrl, String successToken) { this.paymentId = paymentId; this.paymentUrl = paymentUrl; this.successToken = successToken; } // Getters public String getPaymentId() { return paymentId; } public String getPaymentUrl() { return paymentUrl; } public String getSuccessToken() { return successToken; } } ``` ```go package main import ( "encoding/json" "fmt" "net/http" "net/url" ) type PaymentResult struct { PaymentID string `json:"payment_id"` PaymentURL string `json:"payment_url"` SuccessToken string `json:"success_token"` } func createCheckoutPayment(orderID string, amount float64) (*PaymentResult, error) { baseURL := "https://api.blockbee.io/checkout/request/" params := url.Values{} params.Add("apikey", "YOUR_API_KEY") params.Add("redirect_url", fmt.Sprintf("https://yoursite.com/success/?order_id=%s", orderID)) params.Add("value", fmt.Sprintf("%.2f", amount)) params.Add("currency", "usd") params.Add("item_description", fmt.Sprintf("Order #%s", orderID)) params.Add("notify_url", fmt.Sprintf("https://yoursite.com/webhook/?order_id=%s", orderID)) params.Add("post", "1") resp, err := http.Get(baseURL + "?" + params.Encode()) if err != nil { return nil, err } defer resp.Body.Close() var result map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, err } if result["status"] == "success" { return &PaymentResult{ PaymentID: result["payment_id"].(string), PaymentURL: result["payment_url"].(string), SuccessToken: result["success_token"].(string), }, nil } message := "Unknown error" if msg, ok := result["message"].(string); ok { message = msg } return nil, fmt.Errorf("failed to create payment: %s", message) } ``` ```bash # Create checkout payment curl -G "https://api.blockbee.io/checkout/request/" \ --data-urlencode "apikey=YOUR_API_KEY" \ --data-urlencode "redirect_url=https://yoursite.com/success/?order_id=12345" \ --data-urlencode "value=10" \ --data-urlencode "currency=usd" \ --data-urlencode "item_description=Order #12345" \ --data-urlencode "notify_url=https://yoursite.com/webhook/?order_id=12345" \ --data-urlencode "post=1" ``` > **INFO: Using Our Official Libraries** >For a simpler integration, you can also use our official libraries. Here's how you would create a checkout payment: > > ```javascript > // Using the official Node.js library > const BlockBee = require('@blockbee/api'); > > const params = { > order_id: orderId, > }; > > const blockbeeParams = { > currency: 'usd', > item_description: `Order #${orderId}`, > post: '1' > }; > > const payment = await BlockBee.paymentRequest( > 'https://yoursite.com/success/', > 'https://yoursite.com/webhook/', > amount.toString(), > params, > blockbeeParams, > 'YOUR_API_KEY' > ); > // payment.payment_url > ``` > > ```php > // Using the official PHP library > $params = ['order_id' => $orderId]; > $blockbeeParams = [ > 'currency' => 'usd', > 'item_description' => 'Order #' . $orderId, > 'post' => '1' > ]; > > $payment = BlockBee\Checkout::payment_request( > 'https://yoursite.com/success/', > 'https://yoursite.com/webhook/', > $amount, > $params, > $blockbeeParams, > 'YOUR_API_KEY' > ); > // $payment->payment_url > ``` > > ```python > # Using the official Python library > from BlockBee import BlockBeeCheckoutHelper > > params = {'order_id': order_id} > bb_params = { > 'currency': 'usd', > 'item_description': f'Order #{order_id}', > 'post': '1' > } > > payment = BlockBeeCheckoutHelper.payment_request( > 'https://yoursite.com/success/', > 'https://yoursite.com/webhook/', > str(amount), > params, > bb_params, > 'YOUR_API_KEY' > ) > # payment.payment_url > ``` > > You can find the full documentation for our libraries here: > - [Node.js Library](/libraries/nodejs) > - [PHP Library](/libraries/php) > - [Python Library](/libraries/python) ### Required Parameters - **`apikey`** - Your BlockBee API key from the [Dashboard](https://dash.blockbee.io/) - **`redirect_url`** - URL where customers are redirected after payment completion. - **`notify_url`** - Webhook URL where BlockBee sends payment notifications. - **`value`** - Payment amount in your configured FIAT currency. ### Optional Parameters - **`currency`** - Override the default FIAT currency set in your [Payment Settings](https://dash.blockbee.io/profile/checkout) (USD, EUR, GBP, etc.). - **`item_description`** - Description that appears on the checkout page. - **`expire_at`** - Payment expiration time (Unix timestamp, minimum 1 hour). - **`post`** - Set to `1` to receive webhooks as POST requests. ### Response The API returns a JSON response containing the `payment_url`. ```json { "status": "success", "payment_id": "fG78jtx96ugjtu0eIbeLmFB9z0feJf9N", "success_token": "fG78jtx96ugjtu0eIbeLmFB9z0feJf9NfG78jtx96ugjtu0eIbeLmFB9z0feJf9N", "payment_url": "https://pay.blockbee.io/payment/fG78jtx96ugjtu0eIbeLmFB9z0feJf9N" } ``` > **TIP** >**API Reference:** For complete response field documentation, see our [Checkout Request API Reference](/api/checkoutrequest). ## Step 2: Redirect Customer to Payment After creating the payment request in Step 1, you will receive a `payment_url` in the API response. Simply redirect your customer to this URL. BlockBee's hosted page takes over from here, handling everything from coin selection to payment confirmation. ```javascript // Redirect customer to payment page window.location.href = paymentResult.paymentUrl; ``` ```php // Redirect customer to payment page header('Location: ' . $paymentResult['paymentUrl']); exit(); ``` ```python # Flask example from flask import redirect @app.route('/create-payment') def create_payment(): # Assume create_checkout_payment is defined as in Step 1 payment_result = create_checkout_payment(order_id='123', amount=10.50) return redirect(payment_result['paymentUrl']) ``` ```ruby # Ruby on Rails example def create # Assume create_checkout_payment is defined as in Step 1 payment_result = create_checkout_payment(order_id: '123', amount: 10.50) redirect_to payment_result[:paymentUrl], allow_other_host: true end ``` ## Step 3: Handle the Webhook This is the final and most important step. When a payment is made, BlockBee will send a notification (webhook) to the `notify_url` you provided. Your application needs to listen for this webhook to mark the order as paid. ### Webhook Endpoint Create an endpoint in your application to receive the webhook. The payload will contain all the details of the transaction. ```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 { is_paid, payment_id, order_id, paid_amount, paid_coin, txid, status } = req.body; // Note: order_id comes from the custom parameter you added to notify_url when creating the payment // Use the payment_id to prevent processing the same webhook twice if (isWebhookAlreadyProcessed(payment_id)) { return res.status(200).send('*ok*'); } // Check if the payment is complete and the status is 'done' if (is_paid === 1 && status === 'done') { // 1. Find the order in your database using the order_id // 2. Update the order status to "paid" and store payment details // e.g., db.orders.update({ id: order_id }, { // status: 'paid', // paid_amount: paid_amount, // paid_coin: paid_coin, // txid: txid // }); // 3. Fulfill the order (e.g., ship product, grant access) } // Mark this webhook as processed using its unique payment_id markWebhookAsProcessed(payment_id); // Respond with *ok* to let BlockBee know you've received the webhook res.status(200).send('*ok*'); }); ``` ```php query("UPDATE orders SET status = 'paid', paid_amount = '{$paid_amount}', paid_coin = '{$paid_coin}', txid = '{$txid}' WHERE id = '{$orderId}'"); // 3. Fulfill the order (e.g., ship product, grant access) } // Mark this webhook as processed using its unique payment_id markWebhookAsProcessed($payment_id); // Respond with *ok* to let BlockBee know you've received the webhook 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() is_paid = data.get('is_paid') payment_id = data.get('payment_id') order_id = data.get('order_id') # This comes from the custom parameter you added to notify_url paid_amount = data.get('paid_amount') paid_coin = data.get('paid_coin') txid = data.get('txid') status = data.get('status') # Use the payment_id to prevent processing the same webhook twice if is_webhook_already_processed(payment_id): return '*ok*', 200 # Check if the payment is complete and status is 'done' if is_paid == 1 and status == 'done': # 1. Find the order in your database # 2. Update the order status to "paid" and store payment details # e.g., Order.objects.filter(id=order_id).update( # status='paid', # paid_amount=paid_amount, # paid_coin=paid_coin, # txid=txid # ) # 3. Fulfill the order (e.g., ship product, grant access) # Mark this webhook as processed using its unique payment_id mark_webhook_as_processed(payment_id) # Respond with *ok* to let BlockBee know you've received the webhook return '*ok*', 200 ``` ```ruby # Ruby on Rails webhook handler class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token def payment # 1. Verify the webhook signature unless verify_webhook_signature(request) render plain: 'Unauthorized', status: :unauthorized return end # 2. Extract data from the payload is_paid = params[:is_paid] payment_id = params[:payment_id] order_id = params[:order_id] # This comes from the custom parameter you added to notify_url paid_amount = params[:paid_amount] paid_coin = params[:paid_coin] txid = params[:txid] status = params[:status] # 3. Use the payment_id to prevent processing the same webhook twice if webhook_already_processed?(payment_id) render plain: '*ok*', status: :ok return end # 4. Check if the payment is complete and the status is 'done' if is_paid == '1' && status == 'done' # 1. Find the order in your database # 2. Update the order status to "paid" and store payment details # e.g., Order.where(id: order_id).update_all( # status: 'paid', # paid_amount: paid_amount, # paid_coin: paid_coin, # txid: txid # ) # 3. Fulfill the order (e.g., ship product, grant access) end # 5. Mark this webhook as processed using the payment_id mark_webhook_as_processed(payment_id) # 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("payment")] public async Task PaymentWebhook([FromBody] PaymentWebhookPayload payload) { // 1. Verify the webhook signature if (!VerifyWebhookSignature(Request)) { return Unauthorized("Unauthorized"); } // 2. Extract data from the payload var isPaid = payload.IsPaid; var paymentId = payload.PaymentId; var orderId = payload.OrderId; // This comes from the custom parameter you added to notify_url var paidAmount = payload.PaidAmount; var paidCoin = payload.PaidCoin; var txid = payload.Txid; var status = payload.Status; // 3. Use the payment_id to prevent processing the same webhook twice if (IsWebhookAlreadyProcessed(paymentId)) { return Ok("*ok*"); } // 4. Check if the payment is complete and the status is 'done' if (isPaid == "1" && status == "done") { // 1. Find the order in your database // 2. Update the order status to "paid" and store payment details // e.g., await _dbContext.Orders.Where(o => o.Id == orderId) // .ExecuteUpdateAsync(o => o // .SetProperty(p => p.Status, "paid") // .SetProperty(p => p.PaidAmount, paidAmount) // .SetProperty(p => p.PaidCoin, paidCoin) // .SetProperty(p => p.Txid, txid)); // 3. Fulfill the order (e.g., ship product, grant access) } // 5. Mark this webhook as processed using the payment_id MarkWebhookAsProcessed(paymentId); // Always respond to stop retries return Ok("*ok*"); } } public class PaymentWebhookPayload { [JsonProperty("is_paid")] public string IsPaid { get; set; } [JsonProperty("payment_id")] public string PaymentId { get; set; } [JsonProperty("order_id")] public string OrderId { get; set; } [JsonProperty("paid_amount")] public string PaidAmount { get; set; } [JsonProperty("paid_coin")] public string PaidCoin { get; set; } [JsonProperty("txid")] public string Txid { get; set; } [JsonProperty("status")] public string Status { get; set; } } ``` ```java // Spring Boot webhook handler @RestController @RequestMapping("/webhook") public class WebhookController { @PostMapping("/payment") public ResponseEntity paymentWebhook(@RequestBody PaymentWebhookPayload payload) { // 1. Verify the webhook signature if (!verifyWebhookSignature(request)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized"); } // 2. Extract data from the payload String isPaid = payload.getIsPaid(); String paymentId = payload.getPaymentId(); String orderId = payload.getOrderId(); // This comes from the custom parameter you added to notify_url String paidAmount = payload.getPaidAmount(); String paidCoin = payload.getPaidCoin(); String txid = payload.getTxid(); String status = payload.getStatus(); // 3. Use the payment_id to prevent processing the same webhook twice if (isWebhookAlreadyProcessed(paymentId)) { return ResponseEntity.ok("*ok*"); } // 4. Check if the payment is complete and the status is 'done' if ("1".equals(isPaid) && "done".equals(status)) { // 1. Find the order in your database // 2. Update the order status to "paid" and store payment details // e.g., orderRepository.updateOrderStatus(orderId, "paid", paidAmount, paidCoin, txid); // 3. Fulfill the order (e.g., ship product, grant access) } // 5. Mark this webhook as processed using the payment_id markWebhookAsProcessed(paymentId); // Always respond to stop retries return ResponseEntity.ok("*ok*"); } } public class PaymentWebhookPayload { private String isPaid; private String paymentId; private String orderId; private String paidAmount; private String paidCoin; private String txid; private String status; // Getters and setters public String getIsPaid() { return isPaid; } public void setIsPaid(String isPaid) { this.isPaid = isPaid; } public String getPaymentId() { return paymentId; } public void setPaymentId(String paymentId) { this.paymentId = paymentId; } public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getPaidAmount() { return paidAmount; } public void setPaidAmount(String paidAmount) { this.paidAmount = paidAmount; } 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; } } ``` ```go // Go webhook handler package main import ( "encoding/json" "log" "net/http" ) type PaymentWebhookPayload struct { IsPaid string `json:"is_paid"` PaymentID string `json:"payment_id"` OrderID string `json:"order_id"` PaidAmount string `json:"paid_amount"` PaidCoin string `json:"paid_coin"` Txid string `json:"txid"` Status string `json:"status"` } func paymentWebhookHandler(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 PaymentWebhookPayload if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // 3. Use the payment_id to prevent processing the same webhook twice if isWebhookAlreadyProcessed(payload.PaymentID) { w.WriteHeader(http.StatusOK) w.Write([]byte("*ok*")) return } // 4. Check if the payment is complete and the status is 'done' if payload.IsPaid == "1" && payload.Status == "done" { // 1. Find the order in your database // 2. Update the order status to "paid" and store payment details // e.g., updateOrderStatus(payload.OrderID, "paid", payload.PaidAmount, payload.PaidCoin, payload.Txid) // 3. Fulfill the order (e.g., ship product, grant access) } // 5. Mark this webhook as processed using the payment_id markWebhookAsProcessed(payload.PaymentID) // Always respond to stop retries w.WriteHeader(http.StatusOK) w.Write([]byte("*ok*")) } ``` ```bash # Test webhook with curl (for development/testing) curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -d '{ "is_paid": "1", "payment_id": "fG78jtx96ugjtu0eIbeLmFB9z0feJf9N", "order_id": "12345", "paid_amount": "1.23", "paid_coin": "btc", "txid": "0xa7551df44e487f9c0507d68d90193cde2604dfcefdc975bae54535a2e0f80b32", "status": "done" }' # Expected response: *ok* ``` > **TIP** >**Full Webhook Fields:** For a complete list of all fields provided in the webhook payload, please see our [Checkout Payments Webhook reference](/webhooks/checkout-payments-webhook). ### Best Practices - **Verify Signatures:** Always verify the webhook signature to ensure the request is from BlockBee. - **Idempotency:** Use the `payment_id` from the webhook payload to ensure you only process each transaction once. - **Respond Quickly:** Always respond with a `*ok*` and a `200` status code to prevent BlockBee from resending the webhook. - **Asynchronous Processing:** For long-running tasks (like calling other APIs), process them in a background job after responding to the webhook. > **TIP** >**Implementation Guide:** For detailed instructions and code examples on how to verify webhook signatures, please see our [Verify Webhook Signature guide](/webhooks/verify-webhook-signature). ### Alternative: Checking Logs Manually While webhooks are recommended, you can also check the payment status by polling our [Checkout Logs API endpoint](/api/checkoutlogs). This can be useful for reconciliation or as a backup if your webhook endpoint fails. You will need the `payment_id` returned in Step 1. > **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 logs = await BlockBee.paymentLogs(paymentId, 'YOUR_API_KEY'); > // logs.notifications contains the payment history > ``` > > ```php > // Using the official PHP library > $logs = BlockBee\Checkout::payment_logs($paymentId, 'YOUR_API_KEY'); > // $logs->notifications contains the payment history > ``` > > ```python > # Using the official Python library > from BlockBee import BlockBeeCheckoutHelper > > logs = BlockBeeCheckoutHelper.payment_logs(payment_id, 'YOUR_API_KEY') > # logs['notifications'] contains the payment history > ``` ## Testing Your Integration Test your checkout 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) - Real blockchain testing without high costs ### 2. Test with a Small Amount - Create a checkout payment for a small value (e.g., $2.00) - Complete the payment on the hosted page using Litecoin - This covers all fees with minimal cost (typically under $0.10) - Allows you to test both pending and confirmed webhook states ### 3. Testing Checklist - ✅ Payment creation works as expected - ✅ Redirect to hosted checkout page is successful - ✅ Webhooks are received for pending and confirmed states - ✅ Order status is updated correctly in your system - ✅ Customer is notified of successful payment ### 4. Local Webhook Testing To test webhooks on your local machine, you need to expose your server to the internet. `ngrok` is a great tool for this. ```bash # Use ngrok for local webhook testing ngrok http 3000 # Your test webhook URL: https://abc12345.ngrok.io/webhook # Use this as the notify_url when creating the payment. ``` > **SUCCESS** >**Ready for production?** Once testing is complete, switch to your production API Key and ensure your `notify_url` and `redirect_url` point to your live production server.