Base URL https://keystoneprotocol.io · all responses are JSON · IDs are returned as strings.
Create an account on the signup page — you get a Starter key instantly, no card. Keep it somewhere safe; it's shown once.
Pass the key as a bearer token. This returns index totals and the current lag:
curl https://keystoneprotocol.io/v1/stats \ -H "Authorization: Bearer $KEY"
const res = await fetch("https://keystoneprotocol.io/v1/stats", {
headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` },
});
const data = await res.json();
console.log(data);
import os, requests
r = requests.get(
"https://keystoneprotocol.io/v1/stats",
headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
)
print(r.json())
Pull an account's recent activity (below), or on Pro+ get a signed webhook the instant something happens. Check your usage anytime on your dashboard.
Every request except /v1/health, /v1/plans, and /v1/public-stats needs your key as a bearer token. Usage is metered per your plan; over-limit requests return 429.
Recent transactions touching an account (from tx metadata), most-recent first.
curl "https://keystoneprotocol.io/v1/accounts/rYourAccount.../activity?limit=5" \ -H "Authorization: Bearer $KEY"
const account = "rYourAccount...";
const res = await fetch(
`https://keystoneprotocol.io/v1/accounts/${account}/activity?limit=5`,
{ headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` } },
);
const { activity } = await res.json();
for (const tx of activity) console.log(tx.ledger_index, tx.tx_type, tx.tx_hash);
import os, requests
account = "rYourAccount..."
r = requests.get(
f"https://keystoneprotocol.io/v1/accounts/{account}/activity",
headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
params={"limit": 5},
)
for tx in r.json()["activity"]:
print(tx["ledger_index"], tx["tx_type"], tx["tx_hash"])
Payments to or from an account — XRP or issued-currency.
Index totals + current indexing lag.
Subscribe an HTTPS endpoint; we POST a signed event whenever a matching transaction is validated.
| field | meaning |
|---|---|
url | your HTTPS endpoint (required) |
watch_account | fire only for this account (optional) |
watch_tx_type | e.g. Payment (optional) |
direction | any · incoming · outgoing |
min_drops | minimum XRP amount in drops (optional) |
curl -X POST https://keystoneprotocol.io/v1/subscriptions \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{"url":"https://you.example/hook","watch_tx_type":"Payment","min_drops":1000000000}'
# → { "id":"…", "secret":"whsec_…" } (the secret signs your webhooks — shown once)
const res = await fetch("https://keystoneprotocol.io/v1/subscriptions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XRPL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://you.example/hook",
watch_tx_type: "Payment",
min_drops: 1_000_000_000, // 1000 XRP
}),
});
const { id, secret } = await res.json();
console.log("store this signing secret:", secret);
import os, requests
res = requests.post(
"https://keystoneprotocol.io/v1/subscriptions",
headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
json={
"url": "https://you.example/hook",
"watch_tx_type": "Payment",
"min_drops": 1_000_000_000, # 1000 XRP
},
)
data = res.json()
print("store this signing secret:", data["secret"])
We sign every webhook: X-XRPL-Signature: sha256=HMAC_SHA256(secret, rawBody). Compute the same HMAC over the raw request body and reject anything that doesn't match. Deliveries are at-least-once — dedupe on X-XRPL-Delivery.
# Signature verification is done in your webhook receiver — see the # JavaScript or Python tab for a copy-paste verifier.
import crypto from "node:crypto";
// Express: use express.raw({ type: "application/json" }) so req.body is the raw Buffer.
function verify(rawBody, header, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return header === expected;
}
app.post("/hook", express.raw({ type: "*/*" }), (req, res) => {
if (!verify(req.body, req.get("X-XRPL-Signature"), process.env.WHSEC))
return res.status(401).end();
const event = JSON.parse(req.body.toString());
console.log(event.event, event.tx_hash, event.data);
res.json({ received: true });
});
import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WHSEC = "whsec_..."
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(header or "", expected)
@app.post("/hook")
def hook():
if not verify(request.get_data(), request.headers.get("X-XRPL-Signature"), WHSEC):
abort(401)
event = request.get_json()
print(event["event"], event["tx_hash"], event["data"])
return {"received": True}
Payments over a ledger range. Row count is metered against your monthly export quota.
curl "https://keystoneprotocol.io/v1/export/payments?from_ledger=105000000&to_ledger=105001000&limit=10000" \ -H "Authorization: Bearer $KEY" -o payments.json
const res = await fetch(
"https://keystoneprotocol.io/v1/export/payments?from_ledger=105000000&to_ledger=105001000&limit=10000",
{ headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` } },
);
const { count, payments } = await res.json();
console.log(`${count} payments`);
import os, requests
r = requests.get(
"https://keystoneprotocol.io/v1/export/payments",
headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
params={"from_ledger": 105000000, "to_ledger": 105001000, "limit": 10000},
)
data = r.json()
print(data["count"], "payments")
Your plan, limits, and current-month usage — or manage it all on your dashboard.
| code | meaning |
|---|---|
401 | missing / invalid API key |
403 | feature not on your plan (upgrade) |
429 | rate limit or monthly quota exceeded |
404 | not found |