Build it with an AI agent
Give this to your AI agent
Paste it into Claude Code, Cursor or any agent that can read a URL. It reads the OpenAPI contract and the signing recipe, and follows the rules for handling real funds.
Read https://www.payaries.com/ai-agents.md and integrate Payaries crypto payments into this project. Follow its rules for handling real funds.Never paste your API secret into an agent. Keep it in an environment variable.
01The recipe
X-Api-Key: <your api key>
X-Timestamp: <current unix time in milliseconds>
X-Signature: hex( HMAC-SHA256( apiSecret,
timestamp + "\n" + METHOD + "\n" + requestTarget + "\n" + body ) )METHODis upper-case.requestTargetis the path plus the query string, exactly as you send it, such as/v1/invoices?status=settled&limit=10. If a proxy rewrites paths, sign the path the gateway receives.bodyis the JSON you send, compact (JSON.stringifyoutput). A request with no body, including everyGET, signs the two characters{}.- The digest is lower-case hex.
- The secret is your API secret. The webhook secret is a different value that verifies Payaries's calls to you.
02Test vectors
Reproduce both signatures below before you make a real call. They use a made-up secret and a fixed timestamp.
| Input | Value |
|---|---|
| API secret | psk_example_secret_not_real |
X-Timestamp | 1725700000000 |
| Vector 1 | POST /v1/invoices |
| Vector 1 body | {"referenceId":"order_1","fiat":{"amount":"49.99","currency":"USD"},"crypto":{"asset":"USDT_TRC20","chainId":"tron-mainnet"}} |
Vector 1 X-Signature | 73fac04526b513fd06ff75f45cbe1a944541bd70f372805d9d11e6cca59f32a6 |
| Vector 2 | GET /v1/invoices?status=settled&limit=10 (body {}) |
Vector 2 X-Signature | 70ab5601cea9b9929f8f2a1db0e2c07ea4dd67cf065d076ea4423565b5a8924c |
03Node.js
const crypto = require('crypto');
function sign(apiSecret, method, requestTarget, body) {
const timestamp = Date.now().toString(); // unix milliseconds
const payload = JSON.stringify(body ?? {}); // compact; send exactly this string
const message = timestamp + '\n' + method.toUpperCase() + '\n' + requestTarget + '\n' + payload;
const signature = crypto.createHmac('sha256', apiSecret).update(message).digest('hex');
return { timestamp, signature, payload };
}
async function call(method, requestTarget, body) {
const { timestamp, signature, payload } = sign(process.env.PAYARIES_API_SECRET, method, requestTarget, body);
const res = await fetch('https://bqpay-api-gateway.buildquick.tech' + requestTarget, {
method,
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.PAYARIES_API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body: method === 'GET' ? undefined : payload,
});
return { status: res.status, body: await res.json() };
}04Python
import hashlib, hmac, json, os, time
import requests
def call(method, request_target, body=None):
timestamp = str(int(time.time() * 1000)) # unix milliseconds
# Compact separators and ensure_ascii=False give the same bytes JSON.stringify would.
payload = json.dumps(body if body is not None else {}, separators=(",", ":"), ensure_ascii=False)
message = f"{timestamp}\n{method.upper()}\n{request_target}\n{payload}"
signature = hmac.new(os.environ["PAYARIES_API_SECRET"].encode(), message.encode(), hashlib.sha256).hexdigest()
return requests.request(
method,
"https://bqpay-api-gateway.buildquick.tech" + request_target,
headers={
"Content-Type": "application/json",
"X-Api-Key": os.environ["PAYARIES_API_KEY"],
"X-Timestamp": timestamp,
"X-Signature": signature,
},
data=None if method == "GET" else payload.encode(), # send the exact bytes you signed
timeout=30,
)ensure_ascii=False matters: Python escapes non-ASCII characters by default and JavaScript doesn't, so a body with an accented customer name would otherwise be signed differently from how the gateway re-serialises it.
05Go
package payaries
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"time"
)
// Sign returns the X-Timestamp and X-Signature header values. body must be the exact JSON string you
// will send ("{}" for a request with no body). Marshal the request once and reuse those bytes.
func Sign(apiSecret, method, requestTarget, body string) (timestamp, signature string) {
timestamp = strconv.FormatInt(time.Now().UnixMilli(), 10)
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(timestamp + "\n" + method + "\n" + requestTarget + "\n" + body))
return timestamp, hex.EncodeToString(mac.Sum(nil))
}Marshal the request once and reuse those bytes for both the signature and the body. Go's encoding/json sorts the keys of a map, so build the body from a struct, or sign the same string you send.
06curl and openssl
API_KEY="$PAYARIES_API_KEY"; API_SECRET="$PAYARIES_API_SECRET"
BASE=https://bqpay-api-gateway.buildquick.tech
TS=$(($(date +%s) * 1000)) # unix milliseconds
TARGET='/v1/invoices?status=settled&limit=10' # path + query, exactly as sent
BODY='{}' # a GET signs the empty object
SIG=$(printf '%s\n%s\n%s\n%s' "$TS" GET "$TARGET" "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed 's/^.* //')
curl -s "$BASE$TARGET" \
-H "X-Api-Key: $API_KEY" -H "X-Timestamp: $TS" -H "X-Signature: $SIG"07Rules that cause most failures
- Sign the bytes you send. Build the body string once, hash it, and send that same string.
- One signature, one request. For
POST,PATCHandDELETEa signature works once. Re-sending it within its window is401 Request signature has already been used. Sign every retry afresh. Reads may repeat. - Keep the clock honest. A timestamp more than five minutes off is rejected.
- Amounts are strings. Send
"49.99", not49.99, so no float formatting can differ between systems. - Don't retry a failed signature in a loop. Thirty failed authentications from one IP in ten minutes lock that IP out for ten minutes (
429). - Never sign in the browser. The secret would ship to every visitor.
The Node SDK signs for you. If you build an HTTP client in another language, write a unit test that reproduces the vectors above.
Questions
- What does a Payaries API signature cover?
- The timestamp in milliseconds, the upper-case HTTP method, the request path plus query string exactly as sent, and the compact JSON body (the two characters {} when there is no body), joined by newlines and signed with HMAC-SHA256 using the API secret, as lower-case hex.
- Why do I get 'Request signature has already been used'?
- Each signature on a POST, PATCH or DELETE works once. A retry, including an automatic retry by your HTTP client that replays the same headers, must be signed afresh with a new timestamp.
- What do I sign for a GET request?
- The same message with the two characters {} as the body, and the query string included in the request target.
Keep reading
- A crypto payment API that behaves like the rest of your stackA REST crypto payment API: create an invoice, get a one-time deposit address, receive signed webhooks and withdraw on demand. HMAC-signed and idempotent.
- Crypto payment API quickstart: from signup to your first payoutTake your first crypto payment through the Payaries API: create a key, submit the business profile, create an invoice, handle the webhook and withdraw. Real requests and responses.
- How to verify crypto payment webhook signaturesVerify a crypto payment webhook's HMAC-SHA256 signature against the raw body, guard against replays, dedupe retries and acknowledge fast — with working Node.js code.
- Crypto payment API errors, rate limits and safe retriesEvery status code and error code the Payaries API returns, what each means, whether to retry it, the rate limits, and a retry loop that never double-pays.
- Build a crypto payment integration with an AI agent, safelyEverything an AI agent or coding assistant needs to integrate Payaries: an OpenAPI spec, llms.txt, markdown docs, signing test vectors and the rules for handling real funds safely.
- Withdrawing crypto through the API: quote, idempotency and approvalHow to withdraw crypto through the Payaries API: check the balance, quote the fees, create an idempotent withdrawal, follow its status and understand auto-approval.
- What Big Tech actually wants from AI regulation: one rulebook, not noneBig Tech's 2026 AI regulation ask is one national rulebook that overrides state laws, not more rules. The White House framework, lobbying money, the EU delay.