API Callbacks

A payment callback (payment notification) will be sent to the merchant’s callback_url whenever the order status, refund status, or billing status changes due to a request. The callback_url parameter is defined when creating the object.

📘

Testing

For payment callback handling you can use requestcatcher.com tool.

Callback Format

🚧

Callback data is sent in POST method.

By creating new API App you can choose in which format you want to receive callbacks:

  • Form Encoding (application/x-www-form-urlencoded)
  • JSON (application/json)

Change callback format for existing API App go to Merchant » API » Apps » Edit.

Callback Retry Schedule

📘

CoinGate sends payment notification while your application returns response 200 (OK) HTTP or 204 (No Content) status code.

Retry Policy for Payment Notifications

Payment notifications are sent according to the following schedule based on the retry count:

Retry Count RangeInterval Between Retries
1 – 5Every 1 minute
6 – 10Every 5 minutes
11 – 15Every 10 minutes
16 – 20Every 20 minutes
21 – 25Every 30 minutes
26 – 30Every 1 hour
31 – 35Every 5 hours
36 – 40Every 1 day
41 and aboveCallback is terminated and no more will be sent

Notification Response Timeout

After sending a payment notification, CoinGate waits for a response for 20 seconds.

Conditions for Canceling and Terminating Payment Notifications

A payment notification will be canceled and terminated under the following conditions:

Retry limit reachedDescription
Retry limit reachedCallback was attempted 40 times without success.
Response status: 301 or 302Redirect received — typically happens when using an http URL that redirects to https. Use the correct protocol in your callback URL.
Response status: 401 UnauthorizedServer requires authentication — usually occurs if your site is password-protected. Ensure your callback URL is publicly accessible.
Response status: 403 ForbiddenCallback is explicitly blocked — check your firewall, IP whitelist, or server restrictions to ensure CoinGate is not being denied access.
Callback to TOR networkNotifications to addresses within the TOR network are not allowed.
Callback to private networkNotifications sent to private networks (e.g., localhost, 127.0.0.1, or internal IPs) are not permitted.

IP Addresses

Payment Callbacks are sent from the servers listed in the following public API endpoints:

These endpoints are public and do not require authentication. Each IP address is listed on a new line.

Ensure that your server and any third-party security services (e.g., Cloudflare, Incapsula) allow traffic from these IP addresses. Blocking them may prevent successful callback delivery.

We recommend updating your IP whitelist regularly using the information provided by these endpoints.

Private Nework & Localhost

CoinGate payment callback will not send notifications to private networks (for example: localhost).
In localhost, you can send test payment notification with cURL library:

# https://httpie.io/

http POST "https://coingate.requestcatcher.com/payment" \
    id==343 \
    order_id=="ORDER-1415020039" \
    status=="paid" \
    price_amount==1050.99 \
    price_currency=="USD" \
    receive_currency=="EUR" \
    receive_amount==926.73 \
    pay_amount==4.81849315 \
    pay_currency=="BTC" \
    created_at=="2014-11-03T13:07:28+00:00"
curl --request POST \
     --data-urlencode "id=343" \
     --data-urlencode "order_id=ORDER-1415020039" \
     --data-urlencode "status=paid" \
     --data-urlencode "price_amount=1050.99" \
     --data-urlencode "price_currency=USD" \
     --data-urlencode "receive_currency=EUR" \
     --data-urlencode "receive_amount=926.73" \
     --data-urlencode "pay_amount=4.81849315" \
     --data-urlencode "pay_currency=BTC" \
     --data-urlencode "created_at=2014-11-03T13:07:28+00:00" \
     "https://coingate.requestcatcher.com/payment"

Payment Callback Logs

You can review payment callbacks and your server response to callback in your account panel. Login to your account and locate API » Payment Callbacks.

Accepting Payment Callback

Example: save the code below as accept-coingate-callback.php and execute cURL command in your command line tool:

# https://httpie.io/

http POST "http://localhost/accept-coingate-callback.php?token=5d02161be9bfb6192a33" \
    id==343 \
    order_id=="ORDER-1415020039" \
    status=="paid" \
    price_amount==1050.99 \
    price_currency=="USD" \
    receive_currency=="EUR" \
    receive_amount==926.73 \
    pay_amount==4.81849315 \
    pay_currency=="BTC" \
    created_at=="2014-11-03T13:07:28+00:00"
curl --request POST \
     --data-urlencode "id=343" \
     --data-urlencode "order_id=ORDER-1415020039" \
     --data-urlencode "status=paid" \
     --data-urlencode "price_amount=1050.99" \
     --data-urlencode "price_currency=USD" \
     --data-urlencode "receive_currency=EUR" \
     --data-urlencode "receive_amount=926.73" \
     --data-urlencode "pay_amount=4.81849315" \
     --data-urlencode "pay_currency=BTC" \
     --data-urlencode "created_at=2014-11-03T13:07:28+00:00" \
     "http://localhost/accept-coingate-callback.php?token=5d02161be9bfb6192a33"
<?php

// Database connection
$mysqli = new mysqli('hostname', 'username', 'password', 'database');

// Check connection
if ($mysqli->connect_error) {
    die("Connection failed: " . $mysqli->connect_error);
}

// Prepare and bind to securely fetch the order
$order_id = $_POST['order_id'];
$stmt = $mysqli->prepare("SELECT * FROM orders WHERE id = ?");
$stmt->bind_param("i", $order_id);
$stmt->execute();
$result = $stmt->get_result();
$order = $result->fetch_assoc();
$stmt->close();

// Securely compare the tokens
$received_token = $_POST['token'];
$stored_hashed_token = $order['token'];

if (hash_equals($stored_hashed_token, hash_hmac('sha256', $received_token, 'your_secret_key'))) {
    // Handle CoinGate order status: https://developer.coingate.com/docs/order-statuses
    $status = NULL;

    // Verify payment status
    if ($_POST['status'] === 'paid') {
        if ($_POST['price_amount'] == $order['amount']) {
            $status = 'paid';
        }
    } else {
        $status = $_POST['status'];
    }

    // Update order status if necessary
    if (!is_null($status)) {
        $stmt = $mysqli->prepare("UPDATE orders SET status = ? WHERE id = ?");
        $stmt->bind_param("si", $status, $order_id);
        $stmt->execute();
        $stmt->close();
    }
}

// Close the database connection
$mysqli->close();
?>
class CoingateCallbackController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :create

  def create
    order = Order.find_by(id: params[:order_id])

    if order.present?
      if params[:token].present? && order.token == params[:token]
        status = nil

        if params[:status] == 'paid'
          if params[:price].to_d >= order.amount # in addition you can check currency (params[:currency] == order.currency)
            status = 'paid'
          end
        else
          status = params[:status]
        end

        if status.present?
          order.update_attribute(:status, status)
        end
      end
    end
  end
end