Gateway API
Everything the payment-link dashboard does, over JSON: create links and payments, read status, and receive signed webhooks. No SDK required — plain HTTPS and an API key.
On this page
Getting started
Authentication
Authenticated endpoints take an API key as a bearer token. Create keys in the partner dashboard under API keys — the full key (it starts with ae_) is shown exactly once at creation and cannot be retrieved again. Send it on every request:
Authorization: Bearer ae_your_api_keyKeys are meant for server-to-server use. Never embed one in a website, app, or repository — anyone holding the key can act on your links. Revoke a leaked key from the dashboard; revocation is immediate.
Base URL
All authenticated endpoints live under:
https://api.anonexch.io/v1Public endpoints (no key needed) live on the main origin under https://anonexch.io/api/pay. Requests and responses are JSON; amounts are decimal strings, timestamps are RFC 3339 / ISO 8601. Unknown request fields are rejected.
Rate limits
The authenticated gateway API allows 60 requests per minute per key. On the public endpoints, link and payment creation allow 10 per minute and quotes 30 per minute, per client. Rate-limited requests receive HTTP 429 — back off and retry.
Public endpoints
No account, no key: these endpoints power anonymous links and the customer-facing checkout. They live on the main origin under https://anonexch.io/api/pay.
Create a link
POST/api/pay/links
Payment links can be created anonymously. The response is the only time the link's id is shown — anonymous links cannot be listed or recovered later, and they cannot carry a webhook URL (webhooks are a partner feature, set on links created with an API key). Share the returned link as https://anonexch.io/p/{id}.
| Field | Type | Required | Description |
|---|---|---|---|
| linkType | string | required | payment for a fixed-amount link, donation for an open-amount link. |
| toCoin | string | required | The coin you receive, e.g. btc. |
| toAddress | string | required | Your payout address for toCoin. |
| amount | string | optional | Fixed amount in toCoin. Required for payment links; must be omitted on donation links. |
| description | string | optional | Shown to the customer on the checkout page. |
curl -X POST https://anonexch.io/api/pay/links \
-H 'Content-Type: application/json' \
-d '{
"linkType": "payment",
"toCoin": "btc",
"toAddress": "bc1qyour-btc-address",
"amount": "0.005",
"description": "Invoice #1042"
}'{
"id": "QK7W3MZP9T",
"url": "https://pay.anonexch.io/QK7W3MZP9T",
"linkType": "payment",
"toCoin": "btc",
"amount": "0.005",
"description": "Invoice #1042"
}Link info
GET/api/pay/links/{id}
The public projection of a link — what anyone holding the link URL may learn. The payout address and webhook configuration are never exposed here. On donation links, amount is absent.
curl https://anonexch.io/api/pay/links/QK7W3MZP9T{
"id": "QK7W3MZP9T",
"url": "https://pay.anonexch.io/QK7W3MZP9T",
"linkType": "payment",
"toCoin": "btc",
"amount": "0.005",
"description": "Invoice #1042"
}Quote a payment
POST/api/pay/links/{id}/quote
Price a payment against a link before creating it. Quotes are indicative — the binding send amount is re-computed server-side when the payment is created.
| Field | Type | Required | Description |
|---|---|---|---|
| fromCoin | string | required | The coin the customer pays with. |
| amount | string | optional | Donation links only: what the customer chooses to give, denominated in the link's receive coin. Must be omitted on fixed-amount links, which carry their own. |
curl -X POST https://anonexch.io/api/pay/links/QK7W3MZP9T/quote \
-H 'Content-Type: application/json' \
-d '{"fromCoin": "eth"}'{
"fromCoin": "eth",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"validUntil": "2026-08-16T12:05:00Z"
}Create a payment
POST/api/pay/links/{id}/payments
Start a checkout against a link. The pair is re-priced server-side (the quote the client saw is never trusted) and the response carries the deposit address plus the exact amount the customer must send in fromCoin.
| Field | Type | Required | Description |
|---|---|---|---|
| fromCoin | string | required | The coin the customer pays with. |
| amount | string | optional | Donation links only: the amount the customer gives, in the link's receive coin. Must be omitted on fixed-amount links. |
| refundAddress | string | optional | Optional but recommended — where the deposit returns if the conversion fails. |
curl -X POST https://anonexch.io/api/pay/links/QK7W3MZP9T/payments \
-H 'Content-Type: application/json' \
-d '{
"fromCoin": "eth",
"refundAddress": "0xcustomer-refund-address"
}'{
"id": "XV5R8NDJ2M",
"url": "https://pay.anonexch.io/QK7W3MZP9T?pmt=XV5R8NDJ2M",
"linkId": "QK7W3MZP9T",
"status": "awaiting_deposit",
"depositAddress": "0xdeposit-address",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z"
}A depositMemo field is included when the deposit coin requires a memo or destination tag.
Payment status
GET/api/pay/payments/{id}
The customer-facing status of a payment — the same shape the create call returned, with status advancing through the payment statuses. Poll it, or use the events stream below.
curl https://anonexch.io/api/pay/payments/XV5R8NDJ2M{
"id": "XV5R8NDJ2M",
"url": "https://pay.anonexch.io/QK7W3MZP9T?pmt=XV5R8NDJ2M",
"linkId": "QK7W3MZP9T",
"status": "confirming",
"depositAddress": "0xdeposit-address",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z"
}Events stream (SSE)
GET/api/pay/payments/{id}/events
Every payment has a public Server-Sent Events stream on the main origin — no key needed, so your checkout page can subscribe directly. Each event's data is the same JSON as the payment status endpoint; a new event is sent whenever the projection changes, and the stream ends after a terminal state.
const es = new EventSource('https://anonexch.io/api/pay/payments/PAYMENT_ID/events')
es.onmessage = event => {
const payment = JSON.parse(event.data)
if (payment.status === 'complete') es.close()
}Partner endpoints
With an API key, links belong to your account: they can be listed, edited, and archived, they appear in the dashboard alongside links created there, and they can carry a webhook URL. All endpoints in this group live under https://api.anonexch.io/v1 and require the Authorization header. A 404 means the resource does not exist or is not yours — the API does not distinguish the two.
List links
GET/v1/links
Every payment link on your account, newest first.
curl https://api.anonexch.io/v1/links \
-H 'Authorization: Bearer ae_your_api_key'[
{
"id": "QK7W3MZP9T",
"url": "https://pay.anonexch.io/QK7W3MZP9T",
"linkType": "payment",
"toCoin": "btc",
"amount": "0.005",
"description": "Invoice #1042",
"toAddress": "bc1qyour-btc-address",
"webhookUrl": "https://example.com/hooks/anonexch",
"archived": false,
"createdAt": "2026-08-01T09:30:00Z",
"paymentCount": 12,
"totalReceived": "0.060"
}
]Create a link
POST/v1/links
Same body as the public create, plus webhookUrl — webhooks can only be set on partner links, never on the anonymous public create. Setting a webhook URL returns a webhookSecret exactly once, in this response.
| Field | Type | Required | Description |
|---|---|---|---|
| linkType | string | required | payment or donation. |
| toCoin | string | required | The coin you receive. |
| toAddress | string | required | Your payout address for toCoin. |
| amount | string | optional | Fixed amount in toCoin. Required for payment links; omitted on donation links. |
| description | string | optional | Shown to the customer on the checkout page. |
| webhookUrl | string | optional | HTTPS URL to receive signed webhooks. Partner links only. |
curl -X POST https://api.anonexch.io/v1/links \
-H 'Authorization: Bearer ae_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"linkType": "payment",
"toCoin": "btc",
"toAddress": "bc1qyour-btc-address",
"amount": "0.005",
"description": "Invoice #1042",
"webhookUrl": "https://example.com/hooks/anonexch"
}'{
"id": "QK7W3MZP9T",
"url": "https://pay.anonexch.io/QK7W3MZP9T",
"linkType": "payment",
"toCoin": "btc",
"amount": "0.005",
"description": "Invoice #1042",
"webhookSecret": "whsec_5f8a2c…e4d1"
}Get a link
GET/v1/links/{id}
One link in full — including the payout address and payment stats.
curl https://api.anonexch.io/v1/links/QK7W3MZP9T \
-H 'Authorization: Bearer ae_your_api_key'{
"id": "QK7W3MZP9T",
"url": "https://pay.anonexch.io/QK7W3MZP9T",
"linkType": "payment",
"toCoin": "btc",
"amount": "0.005",
"description": "Invoice #1042",
"toAddress": "bc1qyour-btc-address",
"webhookUrl": "https://example.com/hooks/anonexch",
"archived": false,
"createdAt": "2026-08-01T09:30:00Z",
"paymentCount": 12,
"totalReceived": "0.060"
}Update a link
PATCH/v1/links/{id}
Send only the fields you want to change. Setting a webhookUrl here returns a webhookSecret exactly once, in this response.
| Field | Type | Required | Description |
|---|---|---|---|
| description | string | optional | New customer-facing description. |
| webhookUrl | string | optional | HTTPS webhook URL; setting one returns a new webhookSecret once. |
| amount | string | optional | New fixed amount (payment links). |
| toAddress | string | optional | New payout address. |
| archived | boolean | optional | Archive or un-archive the link. |
curl -X PATCH https://api.anonexch.io/v1/links/QK7W3MZP9T \
-H 'Authorization: Bearer ae_your_api_key' \
-H 'Content-Type: application/json' \
-d '{"archived": true}'Rotate webhook secret
POST/v1/links/{id}/webhook-secret
Issues a new signing secret for the link's webhooks. The new secret is returned exactly once; rotating invalidates the old secret immediately.
curl -X POST https://api.anonexch.io/v1/links/QK7W3MZP9T/webhook-secret \
-H 'Authorization: Bearer ae_your_api_key'{
"webhookSecret": "whsec_9c1b7f…a230"
}Link payments
GET/v1/links/{id}/payments
The link's payment history, newest first.
curl https://api.anonexch.io/v1/links/QK7W3MZP9T/payments \
-H 'Authorization: Bearer ae_your_api_key'[
{
"id": "XV5R8NDJ2M",
"status": "complete",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z",
"completedAt": "2026-08-16T12:19:02Z"
}
]List payments
GET/v1/payments
Every payment on your account, across all links and one-off payments, newest first. linkId identifies the link a payment was made against; one-off payments omit it.
curl https://api.anonexch.io/v1/payments \
-H 'Authorization: Bearer ae_your_api_key'[
{
"id": "XV5R8NDJ2M",
"url": "https://pay.anonexch.io/QK7W3MZP9T?pmt=XV5R8NDJ2M",
"linkId": "QK7W3MZP9T",
"status": "complete",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z",
"completedAt": "2026-08-16T12:19:02Z"
}
]One-off payment
POST/v1/payments
A payment can also be created directly, without a stored link — useful when your backend generates a charge per checkout. The response carries the deposit address and the amount the customer must send in fromCoin. One-off payments carry no webhook — create a link if you want callbacks.
| Field | Type | Required | Description |
|---|---|---|---|
| toCoin | string | required | The coin you receive. |
| toAddress | string | required | Your payout address for toCoin. |
| amount | string | required | The amount to receive, in toCoin. |
| fromCoin | string | required | The coin the customer pays with. |
| refundAddress | string | optional | Optional but recommended — where the deposit returns if the conversion fails. |
curl -X POST https://api.anonexch.io/v1/payments \
-H 'Authorization: Bearer ae_your_api_key' \
-H 'Content-Type: application/json' \
-d '{
"toCoin": "btc",
"toAddress": "bc1qyour-btc-address",
"amount": "0.005",
"fromCoin": "eth",
"refundAddress": "0xcustomer-refund-address"
}'{
"id": "XV5R8NDJ2M",
"status": "awaiting_deposit",
"depositAddress": "0xdeposit-address",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z"
}Get a payment
GET/v1/payments/{id}
Poll a payment's status with your key, or subscribe to the public SSE stream — it needs no key, so your checkout page can listen directly.
curl https://api.anonexch.io/v1/payments/XV5R8NDJ2M \
-H 'Authorization: Bearer ae_your_api_key'{
"id": "XV5R8NDJ2M",
"url": "https://pay.anonexch.io/QK7W3MZP9T?pmt=XV5R8NDJ2M",
"linkId": "QK7W3MZP9T",
"status": "sending",
"depositAddress": "0xdeposit-address",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z"
}Webhooks
Partner-only: a webhook URL can be set on links created from the partner dashboard or through this API — never on anonymous links. When a payment against such a link settles, we POST to the URL. Webhook URLs must be HTTPS.
Payload
Events: payment.completed and payment.failed. The body:
{
"event": "payment.completed",
"paymentId": "…",
"linkId": "…",
"status": "complete",
"fromCoin": "eth",
"toCoin": "btc",
"sendAmount": "0.081",
"quotedReceive": "0.005",
"createdAt": "2026-08-16T12:00:00Z",
"completedAt": "2026-08-16T12:19:02Z"
}Signature
Each delivery is signed with your link's secret (it starts with whsec_):
X-AnonExch-Signature: t=1755345542,v1=5f8a2c…e4d1To verify: concatenate the t value, a literal ., and the raw request body; compute HMAC-SHA256 over that string keyed with your whsec_ secret; hex-encode and constant-time compare against v1. Reject the delivery if the signature doesn't match or t is older than your tolerance (5 minutes is a sensible default) — the timestamp is what makes captured deliveries unreplayable. Always verify against the raw bytes, not a re-serialized parse.
Retries
Respond with any 2xx quickly; anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours before the delivery is dropped.
Payment statuses
Payments move through these states, in order, ending in exactly one terminal state:
| awaiting_deposit | Waiting for the customer to send funds to the deposit address. |
| confirming | The deposit was seen and is gathering network confirmations. |
| exchanging | The deposit is being converted into the receive coin. |
| sending | The converted funds are on their way to the payout address. |
| complete | Terminal — the payout was delivered. |
| failed | Terminal — the conversion failed; a refund address, if given, is used. |
| expired | Terminal — no deposit arrived within the payment window. |