For the complete documentation index, see llms.txt. Append
.mdto any page URL for its markdown version.
IP binding helps protect your customers' orders. When you create an order, you tell Banxa which network the customer is on. When someone opens the checkout link, Banxa checks that they're on the same network. If they're not, Banxa can block checkout and cancel the order.
IP binding is on for all new partners, so every buy and sell order must include it. Existing partners need it too: Banxa will contact you with a date before turning it on for your account. Until it's on, leaving the fields out changes nothing.
1. Get the customer's IP address on your server. Take it from the request the customer's browser or app sends to your server to start the order. If your server sits behind a proxy or load balancer, use the customer's address that it forwards to you, not the proxy's own address. Don't reuse an address saved from an earlier login or session.
Create the order from your server, not from the customer's browser or app: your server is where the customer's IP address and your API secret are.
2. Choose hash or range for each address.
| Method | Matches | Needs your API secret | Use it when |
|---|---|---|---|
hash | Only the exact address | Yes | You want the strictest check. |
range | Any address in the same IPv4 /24 or IPv6 /64 network | No | Your customers' addresses change often, for example on mobile networks, or you only have an x-api-key. A range also matches other people on the same network. |
You can use a different method for each address.
3. Add one item per IP version. If you know both the customer's IPv4 and IPv6 address, send both. Customers often reach checkout over IPv6 even when your site saw IPv4. If checkout is opened from an IP version you didn't send, Banxa records it but never blocks the order for it.
An IPv4 address written in IPv6 form, such as ::ffff:203.0.113.7, counts as IPv4: send 203.0.113.7 with "family": "v4".
4. Send ipBinding with the order.
"ipBinding": [
{ "method": "hash", "family": "v4", "value": "31e085138870946bf3e30b6e6709089d14909607b10f1b2138ddc7aaa0d36057" },
{ "method": "range", "family": "v6", "value": "2001:db8:1234:5678::/64" }
]If the customer uses iCloud Private Relay, checkout sees Apple's relay address instead of the one your server saw. For those customers, send "ipBindingOmitted": "private_relay" instead of ipBinding. Never send both.
You can also send originalUrl, the page the customer was on when they started the order. It's optional.
See the full example request.
5. Handle the results.
- If IP binding is on for your account and an order has neither
ipBindingnoripBindingOmitted, the order isn't created and you get a422withIP_BINDING_MISSING. See Error Codes. - If Banxa cancels an order because checkout was opened from a different network, the
cancelledorder webhook includes"reason_code": "IP_BINDING_MISMATCH". See Webhooks.
Banxa sets, per account, whether a mismatch only gets recorded or blocks checkout. There's nothing for you to configure.
A hash value is the HMAC-SHA256 of the IP address, written as hexadecimal:
value = hex(HMAC-SHA256(key = your API secret, message = the IP address as text))- Key: the API secret (HMAC secret) that belongs to the same key pair as the
x-api-keyyou create the order with. Not thex-api-keyitself. If you have several key pairs, use the one that matches thatx-api-key, which may differ from the pair that signs your webhooks. - Message: the IP address in its standard short form. IPv4 as dotted decimal, such as
203.0.113.7. IPv6 in lower case with the longest run of zeros written as::(RFC 5952), such as2001:db8::7rather than2001:0DB8:0:0:0:0:0:7. - Result: 64 hexadecimal characters. Upper or lower case both work.
Keep the key pair until the order is paid: don't delete or revoke it while an order is still unpaid. Banxa checks the IP address every time the customer opens checkout before paying, and it can't check a hash without the key pair.
Use these values to test your code. With the secret example-api-secret:
| Customer's IP address | Send as | value |
|---|---|---|
203.0.113.7 | "family": "v4" | 31e085138870946bf3e30b6e6709089d14909607b10f1b2138ddc7aaa0d36057 |
2001:0DB8:0000:0000:0000:0000:0000:0007 | "family": "v6", hashed as 2001:db8::7 | cee45e0bd6238d4c3daf71fcb7b7f451a3ef50a4ff1f6ab2d31fcd424efdb73b |
Code examples:
import hmac, hashlib, ipaddress
def ip_binding_hash(ip: str, api_secret: str) -> dict:
address = ipaddress.ip_address(ip)
# An IPv4 address written in IPv6 form is sent as IPv4.
if address.version == 6 and address.ipv4_mapped:
address = address.ipv4_mapped
# str() gives the standard short form, including RFC 5952 for IPv6.
value = hmac.new(api_secret.encode(), str(address).encode(), hashlib.sha256).hexdigest()
return {"method": "hash", "family": f"v{address.version}", "value": value}A range value is the customer's network in CIDR form, with every bit after the prefix set to zero:
- IPv4: the
/24network. For203.0.113.7, send203.0.113.0/24. - IPv6: the
/64network. For2001:db8:1234:5678::7, send2001:db8:1234:5678::/64.
Banxa rejects any other prefix length, and a value like 203.0.113.7/24 where the host part isn't zero.
The Node.js, PHP, Java and Swift examples use normaliseIp, parseIp and formatIp from the hashing examples above.
import ipaddress
def ip_binding_range(ip: str) -> dict:
address = ipaddress.ip_address(ip)
if address.version == 6 and address.ipv4_mapped:
address = address.ipv4_mapped
prefix = 24 if address.version == 4 else 64
network = ipaddress.ip_network(f"{address}/{prefix}", strict=False)
return {"method": "range", "family": f"v{address.version}", "value": str(network)}- Create Buy Order and Create Sell Order: the
ipBinding,ipBindingOmittedandoriginalUrlfields - Error Codes:
IP_BINDING_MISSING - Webhooks:
reason_codeon cancelled orders - Going Live