INTEGRATION GUIDE
Make the payment gateway consume authority exactly once.
Strict verification reserves approved authority. A separate, audience-bound processor credential must consume it before provider submission, then commit or release the authenticated outcome.
PURCHASE ENVELOPE
Send final, normalized facts—not payment credentials.
Production loads limits and consent from the immutable mandate registry. The execution envelope carries final transaction facts and a new account-wide idempotency key that remains stable for one attempt. Bind the same final envelope to every transition. MandateShield never submits or deduplicates the provider charge; the payment gateway must refuse to call the provider unless CONSUME succeeds.
{
"protocol": "CUSTOM",
"mandate_id": "mnd_2048",
"agent_id": "agent_travel_07",
"merchant_id": "merchant_rail",
"amount": { "value": 124.90, "currency": "USD" },
"created_at": "<current ISO-8601 timestamp>",
"idempotency_key": "checkout_9f22a1",
"checkout_hash": "sha256:final-checkout-digest",
"intent_hash": "sha256:..."
}
JAVASCRIPT SDK
Separate authority verification from processor enforcement.
import {
createClient,
requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"
const verifier = createClient({
apiKey: process.env.MANDATESHIELD_VERIFY_KEY,
timeoutMs: 10_000
})
const processorGate = createClient({
apiKey: process.env.MANDATESHIELD_PROCESSOR_KEY,
timeoutMs: 10_000
})
const challenge = await verifier.createChallenge({
mandate_id: purchase.mandate_id,
protocol: purchase.protocol,
key_thumbprint: process.env.MANDATESHIELD_KEY_THUMBPRINT
})
// Your trusted issuer signs the canonical purchase digest, challenge nonce,
// and the exact issuer/audience returned for the selected account pin.
const evidence = await trustedIssuer.sign(purchase, {
nonce: challenge.nonce,
issuer: challenge.issuer,
audience: challenge.audience,
issuedAt: new Date().toISOString(),
expiresAt: challenge.expires_at_iso
})
const reservation = requireAllowed(await verifier.verifyAuthority({
envelope: purchase,
evidence
}))
const transitionBase = {
compact: reservation.signed_receipt.compact,
expected_envelope: purchase,
expected_audience: challenge.audience
}
const consumed = await processorGate.transitionExecutionAuthorization({
...transitionBase,
action: "CONSUME",
idempotency_key: `${purchase.idempotency_key}:consume`
})
if (consumed.transition_state !== "CONSUMED") stopPayment()
const outcome = await submitOnceToPaymentProvider(purchase)
if (outcome.status === "UNKNOWN") {
await queueProviderReconciliation(outcome.reference)
throw new Error("Provider outcome requires reconciliation")
}
await processorGate.transitionExecutionAuthorization({
...transitionBase,
action: outcome.status === "COMMITTED" ? "COMMIT" : "RELEASE",
idempotency_key:
`${purchase.idempotency_key}:${outcome.status.toLowerCase()}`,
processor_result: outcome.authenticatedAssertion
})
RAW HTTP
Use the same transition contract in any language.
# VERIFY key: strict ALLOW creates a RESERVED authorization.
curl --fail-with-body \
https://mandateshield.com/api/v2/verify \
-X POST \
-H "content-type: application/json" \
-H "authorization: Bearer $MANDATESHIELD_VERIFY_KEY" \
--data @strict-verification.json
# Validate the full ALLOW invariant and state == RESERVED, then:
# PROCESSOR key: consume exact receipt/envelope/audience before submission.
curl --fail-with-body \
https://mandateshield.com/api/v2/execution-authorizations \
-X POST \
-H "content-type: application/json" \
-H "authorization: Bearer $MANDATESHIELD_PROCESSOR_KEY" \
--data @consume.json
# Only transition_state == CONSUMED unlocks one provider submission.
# Afterwards POST COMMIT or RELEASE with the bound processor_result.
STRICT CRYPTOGRAPHIC PROFILE
Verify signed authority before evaluating policy.
Caller-supplied public keys prove mathematical consistency, not a trusted identity. A live reservation requires an account-pinned key, followed by a separate processor-side CONSUME transition.
const decision = await client.verifyAuthority({
envelope: purchase,
evidence: {
format: "jws",
compact: signedAuthority,
public_key: issuerPublicJwk,
expected_audience: challenge.audience,
expected_nonce: challenge.nonce
}
})
// Confirms a live RESERVED authorization. It does not submit payment.
const reservation = requireAllowed(decision)
await handReservationToTrustedGateway({
compact: reservation.signed_receipt.compact,
envelope: purchase,
audience: challenge.audience
})
OPENAI AGENTS SDK
Attach the remote Streamable HTTP server.
This uses the SDK's published MCPServerStreamableHttp lifecycle. It is framework connectivity, not a MandateShield-specific package.
npm install @openai/agents zod
import {
Agent,
MCPServerStreamableHttp,
run
} from "@openai/agents"
const server = new MCPServerStreamableHttp({
name: "MandateShield",
url: "https://mandateshield.com/api/mcp",
cacheToolsList: true,
errorFunction: null,
requestInit: {
headers: {
authorization:
`Bearer ${process.env.MANDATESHIELD_VERIFY_KEY}`
}
}
})
await server.connect()
try {
const agent = new Agent({
name: "Checkout planner",
instructions:
"Use MandateShield to inspect the final purchase. Never claim that a payment executed.",
mcpServers: [server]
})
const result = await run(agent, checkoutRequest)
console.log(result.finalOutput)
} finally {
await server.close()
}
OpenAI Agents MCP documentation ↗
VERCEL AI SDK
Convert remote MCP tools into AI SDK tools.
The official HTTP transport discovers both schemas. Close short-lived clients after the model run.
npm install ai @ai-sdk/mcp
import { createMCPClient } from "@ai-sdk/mcp"
import { generateText } from "ai"
const mcp = await createMCPClient({
transport: {
type: "http",
url: "https://mandateshield.com/api/mcp",
headers: {
authorization:
`Bearer ${process.env.MANDATESHIELD_VERIFY_KEY}`
}
}
})
try {
const tools = await mcp.tools()
const result = await generateText({
model, // your configured AI SDK model
tools,
prompt: checkoutRequest
})
console.log(result.text)
} finally {
await mcp.close()
}
AI SDK MCP documentation ↗
LANGCHAIN JAVASCRIPT
Let the model reserve; keep execution in the gateway.
There is no claimed first-party LangChain adapter here. This is a standard LangChain tool around the public JavaScript client, with the verification gate in the callback. The PROCESSOR key and provider call remain outside the model tool.
npm install langchain zod
import { createAgent, tool } from "langchain"
import * as z from "zod"
import {
createClient,
requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"
const client = createClient({
apiKey: process.env.MANDATESHIELD_VERIFY_KEY
})
const reservePaymentAuthority = tool(async ({
purchase,
evidence,
processorAudience
}) => {
const decision = await client.verifyAuthority({
envelope: purchase,
evidence: {
...evidence,
expected_audience: processorAudience
}
})
const reservation = requireAllowed(decision)
// The model receives no PROCESSOR key. A trusted gateway owns
// CONSUME -> provider submission -> COMMIT/RELEASE.
await trustedGateway.enqueueReservation({
compact: reservation.signed_receipt.compact,
envelope: purchase,
audience: processorAudience
})
return {
state: "RESERVED",
expires_at: reservation.execution_authorization.expires_at
}
}, {
name: "reserve_payment_authority",
description:
"Reserve final purchase authority for a trusted payment gateway.",
schema: z.object({
purchase: z.record(z.string(), z.unknown()),
evidence: z.record(z.string(), z.unknown()),
processorAudience: z.string().url()
})
})
const agent = createAgent({
model, // your configured LangChain model
tools: [reservePaymentAuthority]
})
LangChain tool documentation ↗