It is a good idea to create an IPN listener page on your website and then specify the URL of the listener page in a "Settings → API" section in your Cryptopay account. It is important to note that you should use only HTTPS URL. Cryptopay then sends a secure POST request containing payment details of all transaction-related events to the URL.

It is Highly recommended to validate callbacks before acting on it inside your system

The IPN listener page contains a custom script or program that waits for the messages, validates them with Cryptopay, and then passes them to various backend applications for processing.

Each callback request contains a X-Cryptopay-Signature header.

"X-Cryptopay-Signature": "7c021857107203da4af1d24007bb0f752e2f04478e5e5bff83719101f2349b54"

This header contains the hex encoded SHA256 HMAC signature of the raw callback body, computed using your callback Secret as the key.

  1. You receive a callback

  2. You use SHA256 for hashing its raw body with a callback Secret

  3. You compare X-Cryptopay-Signature value to the hash you've got after hashing the callback body

class CryptopayCallbackVerifier
def initialize(secret)
@secret = secret
def verify(body, signature)
expected_signature = OpenSSL::HMAC.hexdigest('SHA256', @secret, body)
secure_compare(signature, expected_signature)
# Constant-time comparison algorithm to prevent timing attacks
def secure_compare(str1, str2)
return false if str1.empty? || str2.empty? || str1.bytesize != str2.bytesize
l = str1.unpack("C#{str1.bytesize}")
res = 0
str2.each_byte { |byte| res |= byte ^ l.shift }
secret = 'hzeRDX54BYleXGwGm2YEWR4Ony1_ZU2lSTpAuxhW1gQ'
verifier =
# Raw callback body
body = '{"type":"Invoice","event":"status_changed","data":{"id":"ff48eeba-ab18-4088-96bc-4be10a82b994","status":"completed","status_context":null,"address":"rs9pE6CnNLE8YiTgTwbAk1AkFyS3opsm7K?dt=701","price_amount":"1.0","price_currency":"EUR","pay_amount":"3.113326","pay_currency":"XRP","paid_amount":"3.113326","exchange":{"pair":"XRPEUR","rate":"0.3212"},"transactions":[{"txid":"3EA591FED2F1F61263CB66AAC6BCF520B0714A08F2481D56DE267F31E0C782B9","risk":null}],"name":null,"description":null,"metadata":null,"custom_id":null,"success_redirect_url":null,"created_at":"2019-04-09T15:22:09+00:00","expires_at":"2019-04-09T15:32:09+00:00"}}'
# Value of X-Cryptopay-Signature header
signature = '7c021857107203da4af1d24007bb0f752e2f04478e5e5bff83719101f2349b54'
verifier.verify(body, signature) # => true

Every new callback request will have a new value of the X-Cryptopay-Signature header. Make sure you are comparing the hash to the right header.

A callback Secret is available in the "Settings → API" section in your Cryptopay account.

Cryptopay IPN server expects to get an HTTP 200 response code from you within 10 seconds. If the response code is different to 200 or a deadline is exceeded, we will retry sending callbacks.

30 + num ^ 4 + num seconds where num is 0 to 19 retry

0d 00h 00m 30s
0d 00h 00m 32s
0d 00h 00m 48s
0d 00h 01m 54s
0d 00h 04m 50s
0d 00h 11m 00s
0d 00h 22m 12s
0d 00h 40m 38s
0d 01h 08m 54s
0d 01h 50m 00s
0d 02h 47m 20s
0d 04h 04m 42s
0d 05h 46m 18s
0d 07h 56m 44s
0d 10h 41m 00s
0d 14h 04m 30s
0d 18h 13m 02s
0d 23h 12m 48s
1d 05h 10m 24s
1d 12h 12m 50s