Integration guide
Wire Stvor in front of execution
Stvor integrates as a verification checkpoint before your payment rail, chain broadcast, or internal ledger write. Flow: commit → verify → settle — with a signed Trust Receipt inline from POST /verify for ALLOW and DENY.
Overview
Three HTTP endpoints on a single origin — flat paths, no /api/v1:
POST /commitments— anchor intent at approval time (payloadHash, not raw payload)POST /verify— compare live payment hash → ALLOW or DENY + signed receipt inlinePOST /receipt— optional settlement: attachtxHashafter ALLOW only
/agents/register endpoint. Do not install @stvor/sdk — that is an unrelated legacy library. Self-check with published test vectors before writing client code. Verify receipts in the browser verifier.01 · SDK install
npm install @stvor/clientVerification-only integrations can use @stvor/core for canonical hashing and offline receipt verification without the HTTP client.
import { Stvor } from "@stvor/client";
const stvor = new Stvor({
baseUrl: "https://api.stvor.xyz",
apiKey: process.env.STVOR_KEY!, // sandbox key on stvor.xyz/#try-now
});
const payment = {
to: "0x4c1b82f71a9e3d0c8b5e6f2a1d4c7b9e0f3a8c1d",
amount: "50000.00",
currency: "USDC",
};
// 1. commit — posts payloadHash (fresh nonce each time)
const commitment = await stvor.commit(payment, {
agentId: "agt_sandbox_demo",
nonce: crypto.randomUUID(),
});
// 2. verify — signed receipt inline for ALLOW and DENY
const result = await stvor.verify(
{ from: "agt_sandbox_demo", ...payment },
{ commitmentId: commitment.commitmentId },
);
if (result.decision !== "ALLOW") {
// result.receipt is a signed DENY — verify offline; do not settle
return;
}
// 3. settle on your rail, then optionally attach txHash
await settleOnYourRail(payment);
// await stvor.settle(result.id!, txHash);02 · Pilot onboarding (optional)
I stand up a Stvor checkpoint in front of your execution flow — commit intent, verify at execution, settle only on ALLOW — plus a signed Trust Receipt for every decision. I do the integration. At the end it works and you keep going, or you pay nothing further.
Map your execution path
Wire checkpoint + receipts
Handoff
03 · Checkpoint placement
// WRONG — verify after payment
await stripe.paymentIntents.capture(id);
await stvor.verify(...); // too late
// RIGHT — commit → verify → settle
const payment = { to: "0xVendor...", amount: "50.00", currency: "USDC" };
const commitment = await stvor.commit(payment, { agentId: "agt_01", nonce: crypto.randomUUID() });
const result = await stvor.verify(
{ from: "agt_01", ...payment },
{ commitmentId: commitment.commitmentId },
);
if (result.decision !== "ALLOW") {
await stripe.paymentIntents.cancel(escrowId);
// signed DENY in result.receipt
throw new Error(result.reason);
}
await stripe.paymentIntents.capture(id);04 · API: commit + verify
Commit at intent time — when the user confirms, when the agent proposes a tool call, or when a contract is created. POST /commitments takes payloadHash (SHA-256 of RFC 8785 payment payload), not raw payload. Store the commitmentId; do not mutate the committed hash.
stvor_test_tdesrpqe9auRopdcA2Ab-bFzF2xylw9v. Required header: Authorization: Bearer <key>. Use a fresh nonce per commitment — duplicates return 409.# Payment payload (RFC 8785 — hash the canonical bytes, not this comment block):
# {"amount":"50000.00","currency":"USDC","to":"0x4c1b82f71a9e3d0c8b5e6f2a1d4c7b9e0f3a8c1d"}
# payloadHash = SHA-256(canonical) = b36c8892571afafab33f0f389c62293948a7ff61f3064eca12700582756cda78
# (agentId, nonce) must be unique per commitment — use a fresh nonce every time.
curl -X POST https://api.stvor.xyz/commitments \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer stvor_test_tdesrpqe9auRopdcA2Ab-bFzF2xylw9v' \
-d '{
"agentId": "agt_sandbox_demo",
"payloadHash": "b36c8892571afafab33f0f389c62293948a7ff61f3064eca12700582756cda78",
"alg": "sha256",
"nonce": "<any-unique-string>",
"expiresAt": "2026-12-31T23:59:59.000Z"
}'# Same to / amount / currency as the committed payload → ALLOW (+ signed receipt inline)
curl -X POST https://api.stvor.xyz/verify \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer stvor_test_tdesrpqe9auRopdcA2Ab-bFzF2xylw9v' \
-d '{
"commitmentId": "cmt_FROM_COMMIT_RESPONSE",
"intent": {
"from": "agt_sandbox_demo",
"to": "0x4c1b82f71a9e3d0c8b5e6f2a1d4c7b9e0f3a8c1d",
"amount": "50000.00",
"currency": "USDC"
}
}'# Swap to after commit → PAYLOAD_MISMATCH → signed DENY
curl -X POST https://api.stvor.xyz/verify \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer stvor_test_tdesrpqe9auRopdcA2Ab-bFzF2xylw9v' \
-d '{
"commitmentId": "cmt_FROM_COMMIT_RESPONSE",
"intent": {
"from": "agt_sandbox_demo",
"to": "0x9d0000000000000000000000000000000000000001",
"amount": "50000.00",
"currency": "USDC"
}
}'async function gate(
commitment: { payloadHash: string },
liveParams: Record<string, unknown>,
) {
const hashOk = timingSafeHashMatch(liveParams, commitment.payloadHash);
if (!hashOk) return deny("PAYLOAD_MISMATCH");
return allow();
}05 · Trust Receipt
On ALLOW and DENY, POST /verify returns a signed ES256 (P-256) Trust Receipt inline in the response. Publish keys at /.well-known/public-key and /.well-known/stvor-keys.json so third parties verify offline. See ATS-1 spec.
After settlement on ALLOW, optionally call POST /receipt with txHash to attach on-chain proof. DENY paths do not settle.
06 · Payment rails
Stripe (reference — live)
Manual capture escrow: authorize at funding, capture only after verify returns ALLOW, cancel on DENY. This is the demonstrated reference implementation on nous.stvor.xyz.
Planned
x402 and OrbWallet are on the roadmap. Any HTTP payment API with hold/capture semantics can host the same checkpoint pattern — Stvor is rail-agnostic by design.