A small REST API for websites and Telegram bots: create an invoice, send the customer to the checkout page and receive a signed webhook when the payment lands.
Live key: real payments, credited to your ScarabPay balance.
sk_test_
Sandbox key: test invoices, nothing is charged or credited.
Never put a key in a mobile app, browser code or a public repository. If a key leaks, revoke it from the Developers tab and create a new one.
Sandbox
Use a sk_test_ key to build and test without moving money. Sandbox invoices open the same checkout page with a test banner.
Mark an invoice as paid with POST /invoices/{id}/simulate — mode "full" or "partial".
Webhooks are sent as usual, with "sandbox": true in the body.
Static addresses are live only.
Conventions
Requests and responses are JSON. Send Content-Type: application/json.
Amounts are USD strings with two decimals. Coin amounts are strings without trailing zeros.
Timestamps are ISO 8601 with time zone.
Successful responses wrap the object in "data". Lists add "meta" with page, pages and total; use ?page= and ?per_page= (max 100).
order_id makes invoice creation safe to retry: the same order_id returns the same invoice (200) instead of creating a new one (201).
Errors
Errors return a non-2xx status and a JSON body with a stable code you can switch on.
Status
Code
Meaning
401
unauthorized
Missing, malformed or revoked key.
403
forbidden
Project is not active (not verified or suspended).
404
not_found
Invoice not found in this project and mode.
409
limit_reached
Your tier limit is reached.
409
invoice_not_payable
The invoice can no longer change (paid, expired or cancelled).
422
validation_failed
A field is invalid; see "fields" for details.
422
method_not_allowed
The chosen method is not enabled for this project.
429
rate_limited
Too many requests. Wait and retry.
502
provider_error
Temporary provider error. Retry with the same order_id.
503
coin_unavailable
The coin is temporarily unavailable; offer another method.
422
{
"error": {
"code": "validation_failed",
"message": "Invalid request.",
"fields": { "order_id": ["The order id field is required."] }
}
}
Rate limits
Each API key can make up to 60 requests per minute. After too many failed key attempts from one IP, that IP is blocked for 10 minutes.
On 429, wait a few seconds and retry. Do not poll invoices in a loop; use webhooks.
Resources
Invoices
One invoice per order. The customer picks a coin on the checkout page (or you preselect it) and pays the exact amount shown. An unpaid invoice expires after 30 minutes.
Create an invoice
POST/invoices
Returns 201 with a new invoice, or 200 with the existing one if order_id was used before.
Field
Description
amount
string · required
Amount in USD, 1 to 100000.
order_id
string · required
Your unique order id. Letters, digits and _ - : . only, up to 100.
description
string · optional
Shown to the customer on the checkout page.
customer_ref
string · optional
Your customer id, e.g. tg:55123.
customer_email
string · optional
Customer email for receipts.
return_url
url · optional
Where the “Back to store” button goes after payment.
method
string · optional
Preselect a coin and skip the picker, e.g. usdt_trc20. See /currencies.
Set your webhook URL and copy the signing secret from the Developers tab. We POST a JSON event to your URL on every change. Reply with any 2xx status within 10 seconds.
Headers
Description
X-ScarabPay-Event
Event name.
X-ScarabPay-Delivery
Unique event id. Use it to ignore duplicates.
X-ScarabPay-Signature
Timestamp and HMAC-SHA256 signature. t=<unix>,v1=<hex>
Another payment update on the invoice (e.g. a payment under review).
deposit.received
A deposit landed on a static address.
address.idle
A static address had no deposits for a long time.
address.unbound
A static address was released from its customer.
payout.sent
A payout from your balance was sent.
project.suspended
Your project was suspended.
ping
Test event from the “Send test” button.
Verify signature
Compute HMAC-SHA256 of "t.body" with your webhook secret and compare it to v1 in constant time. Use the raw body exactly as received, and reject timestamps older than 5 minutes.
import hmac, hashlib, os, time
def verify(sig_header: str, body: bytes) -> bool:
parts = dict(p.split("=", 1) for p in sig_header.split(",") if "=" in p)
secret = os.environ["SCARABPAY_WEBHOOK_SECRET"].encode()
expected = hmac.new(secret, parts.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts.get("t", 0))) < 300
return fresh and hmac.compare_digest(expected, parts.get("v1", ""))
# FastAPI / aiohttp: pass request.headers["X-ScarabPay-Signature"] and the raw body bytes
Retries
If your server does not answer with 2xx, we retry up to 8 times: after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h and 12 h. You can also resend any event from the delivery log.
#1+0
#2+1m
#3+5m
#4+15m
#5+1h
#6+3h
#7+6h
#8+12h
Handlers must be idempotent: the same event can arrive more than once. Store X-ScarabPay-Delivery and skip repeats.
Reference
Objects
Invoice
id
Invoice id, starts with inv_.
order_id
Your unique order id. Letters, digits and _ - : . only, up to 100.
status
See invoice statuses.
amount · paid_amount
USD received so far.
method · coin · network
Chosen coin and network, null until selected.
address · coin_amount
Deposit address and exact coin amount to pay.
checkout_url
Hosted payment page. Send the customer here.
sandbox
true for sandbox invoices.
expires_at · paid_at · created_at
ISO 8601 timestamps.
Payment
id
Payment id, starts with pay_.
invoice_id · order_id · customer_ref
Your customer id, e.g. tg:55123.
coin · network · coin_amount
Chosen coin and network, null until selected.
amount · fee · net
Gross USD, our fee and the net credited to you.
tx_hash
Blockchain transaction hash.
status
credited, held, review or sandbox.
Static address
customer_ref
Your customer id, e.g. tg:55123.
network · address
Deposit address and exact coin amount to pay.
memo
Memo / tag when the network needs one (e.g. TON).
total_received
Total USD received on this address.
last_deposit_at · created_at
ISO 8601 timestamps.
Invoice statuses
pending
Waiting for payment.
paid
Fully paid and credited. Deliver the order.
underpaid
Less than the amount arrived. The received part is credited.
expired
Time ran out without payment.
late
Paid after expiry. The payment is held for a check before it is credited.
cancelled
Cancelled by you.
Need help with the integration?
Our support team can review your setup and test events with you.