INTEGRATION GUIDE
Put a fresh provider-bound claim in front of the operation.
The recommended path separates customer reservation, signed permit issuance, one fresh server-side claim, the executor's idempotent provider call, and outcome checking that does not trust the caller report.
ZERO-DEPENDENCY JAVASCRIPT
Vendor one ESM file.
The client uses the runtime's built-in fetch and AbortController. It has no install script, transitive packages or telemetry. Commit the downloaded file so upgrades are explicit and reviewable.
mkdir -p vendor/mandateshield
curl --proto '=https' --tlsv1.2 -fsSLo \
vendor/mandateshield/mandateshield.mjs \
https://mandateshield.com/sdk/v1.13.0/mandateshield.mjs
curl --proto '=https' --tlsv1.2 -fsSLo \
vendor/mandateshield/SHA256SUMS \
https://mandateshield.com/sdk/v1.13.0/SHA256SUMS
(
cd vendor/mandateshield
if command -v sha256sum >/dev/null 2>&1; then
grep ' mandateshield.mjs$' SHA256SUMS | sha256sum -c -
else
grep ' mandateshield.mjs$' SHA256SUMS | shasum -a 256 -c -
fi
)
RUNTIME
Node.js 18+ or a current browser.
Production keys belong only in trusted server code. A browser may use the anonymous sandbox for analysis, but a sandbox result can never pass requireAllowed.
Download JavaScript client →Download offline execution-evidence verifier →Verify SHA-256 checksum →OFFICIAL POSTGRESQL JOURNAL
Replace the production journal placeholder.
Run the first-party DurableGatewayJournal against one customer-controlled PostgreSQL primary. The explicit migration, atomic compare-and-set adapter and readiness check keep database credentials outside MandateShield. All Gate workers must share the same writable, linearizable topology; split-brain remains unsafe.
Open PostgreSQL journal guide →PRIMARY LAST-MILE PROFILE
Supported AP2 approval facts to one exact Stripe request.
Project the supported closed-payment fields without treating that projection as authority, verify the account-pinned signed approval, and preserve the exact envelope through the Stripe account, payee, amount, currency and form-body digest.
Open AP2-to-Stripe seam guide →FIRST-PARTY PAYMENT ADAPTER
Stripe PaymentIntents, behind the same durable gate.
Run the gateway and adapter inside your server. Exact amount, merchant, audience and provider idempotency are bound without sending Stripe credentials or PaymentMethod IDs to MandateShield.
Open Stripe adapter guide →OPTIONAL X402 COMPATIBILITY PROFILE
Add a shared evidence lifecycle without replacing x402.
Native x402 exact/EIP-3009 already supplies value, payee, validity and nonce binding, while payment identifiers and facilitators can provide idempotency, verification and settlement. This optional profile adds the MandateShield reserve, interlock and receipt lifecycle for teams operating both supported paths. Signed payloads, wallet keys and paid content remain customer-side. No facilitator adoption or primary differentiation is claimed.
Open x402 adapter guide →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. The hosted MandateShield service never submits or deduplicates the provider charge. A customer-side Gate adapter may prepare one bound request after CONSUME. In the recommended provider-bound path, it may transmit only after the permit is freshly redeemed at the customer-deployed exclusive executor.
{
"protocol": "CUSTOM",
"mandate_id": "mnd_2048",
"agent_id": "agent_travel_07",
"merchant_id": "merchant_rail",
"amount": { "value": 124.90, "currency": "USD" },
"payee_identity": {
"profile": "MANDATESHIELD_PAYEE_IDENTITY_V1",
"provider": "STRIPE",
"merchant_id": "merchant_rail",
"binding": {
"connected_account_id": "acct_connected_12345",
"merchant_account_id": "acct_connected_12345"
},
"verification": {
"method": "STRIPE_ACCOUNT_CONFIGURATION",
"verifier": "customer-stripe-gateway",
"evidence_ref": "urn:stripe:connected-account:acct_connected_12345"
}
},
"created_at": "<current ISO-8601 timestamp>",
"idempotency_key": "checkout_9f22a1",
"checkout_hash": "sha256:final-checkout-digest",
"intent_hash": "sha256:..."
}
JAVASCRIPT SDK
Use the pinned client for challenge and reservation.
The v1.7 client covers provider binding, advisory permit verification, exact online redemption and execution-receipt verification. The example first stops at the reservation handoff so the PROCESSOR credential stays in the separate trusted edge. Never infer support beyond a client's published feature surface.
import {
createClient,
requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"
const verifier = createClient({
apiKey: process.env.MANDATESHIELD_VERIFY_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
}))
await handToProviderBoundGateway({
compact: reservation.signed_receipt.compact,
receiptId: reservation.signed_receipt.receipt_id,
envelope: purchase,
audience: challenge.audience
})
PROVIDER-BOUND EXECUTION
Issue, redeem, submit with the signed key, then reconcile.
The redeem call belongs inside the customer-deployed exclusive executor—not in the agent, browser, merchant page or ordinary checkout worker. That executor must be the sole holder of the provider credential and permitted egress path. A permit lives for at most 60 seconds. A fresh redemption admits one MandateShield claimant; the exact provider operation must still be idempotent. Offline signature verification remains advisory and returns provider submission permission false.
const base = {
compact: reservation.signed_receipt.compact,
expected_envelope: purchase,
expected_audience: processorAudience
}
// Customer gateway: bind one exact request. This does not permit submission.
const consumed = await post("/api/v2/execution-authorizations", {
...base,
action: "CONSUME",
idempotency_key: `${attemptId}:consume`,
provider_binding: {
profile: "STRIPE_PAYMENT_INTENTS_V1",
environment: "live",
account: purchase.payee_identity.binding.merchant_account_id,
request_id: providerRequestId,
payee_destination:
purchase.payee_identity.binding.connected_account_id,
method: "POST",
resource: "https://api.stripe.com/v1/payment_intents",
body_digest: providerBodyDigest
}
}, process.env.MANDATESHIELD_PROCESSOR_KEY)
if (
consumed.provider_submission_permitted !== false ||
consumed.provider_redemption_required !== true
) throw new Error("Unsafe CONSUME result")
// Customer-deployed exclusive executor: keep the dedicated PROCESSOR and
// provider credentials here, with alternate provider egress blocked.
const permit = consumed.execution_permit
const checked = await post("/api/v2/execution-permits/verify", {
compact: permit.compact,
expected_audience: processorAudience
})
if (checked.provider_submission_permitted !== false) {
throw new Error("Signature verification must remain advisory")
}
const redeemed = await post("/api/v2/execution-permits/redeem", {
compact: permit.compact,
idempotency_key: `${attemptId}:redeem`,
expected_request: {
provider: checked.claims.provider,
payee: checked.claims.payee,
amount: checked.claims.amount,
resource: checked.claims.resource
}
}, process.env.MANDATESHIELD_PROVIDER_PROCESSOR_KEY)
if (
redeemed.provider_submission_permitted !== true ||
redeemed.status !== "CLAIMED" ||
redeemed.idempotent_replay !== false
) throw new Error("No fresh redemption")
// The MandateShield claim is single-winner; provider delivery is not exactly-once.
const outcome = await provider.submit({
...providerRequest,
idempotencyKey: redeemed.provider_idempotency_key
})
const report = await post("/api/v2/provider-submissions", {
provider_submission_id: redeemed.provider_submission_id,
permit_id: redeemed.permit_id,
claim_id: redeemed.claim_id,
payment_reference: outcome.reference,
outcome: outcome.status,
occurred_at: outcome.occurredAt,
provider_observation: outcome.boundedObservation
}, process.env.MANDATESHIELD_PROVIDER_PROCESSOR_KEY)
if (
report.terminal_transition_permitted !== true ||
report.independent_verification !== true ||
!["PROVIDER_API_VERIFIED", "CHAIN_FINALIZED"].includes(
report.evidence_class
) ||
report.authorization_finalized !== true ||
!report.execution_receipt?.compact
) {
await queueProviderReconciliation(redeemed.provider_submission_id)
throw new Error("Provider outcome remains fail-closed")
}
const terminal = report.execution_receipt
RAW HTTP
Use the complete safe path in any language.
# provider_binding is mandatory for accounts created at or after
# 2026-07-26T12:01:24.000Z; older accounts alone retain legacy omission.
# New integrations must always send the exact binding.
#
# 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 with an exact provider_binding issues a permit.
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
# CONSUME must return provider_submission_permitted=false and a signed permit.
# Signature-only verification never grants:
curl --fail-with-body \
https://mandateshield.com/api/v2/execution-permits/verify \
-X POST -H "content-type: application/json" --data @verify-permit.json
# Customer-deployed exclusive executor: redeem exact bindings with its live
# audience-bound PROCESSOR credential. Only one fresh MandateShield claim returns true.
curl --fail-with-body \
https://mandateshield.com/api/v2/execution-permits/redeem \
-X POST \
-H "content-type: application/json" \
-H "authorization: Bearer $MANDATESHIELD_PROVIDER_PROCESSOR_KEY" \
--data @redeem-permit.json
# Use the signed provider_idempotency_key for the provider operation.
# Report the exact submission, permit, claim, reference and bounded caller hint.
curl --fail-with-body \
https://mandateshield.com/api/v2/provider-submissions \
-X POST \
-H "content-type: application/json" \
-H "authorization: Bearer $MANDATESHIELD_PROVIDER_PROCESSOR_KEY" \
--data @provider-submission.json
# Only provider-API or finalized-chain evidence checked without trusting the caller may auto-finalize.
# PENDING, UNKNOWN and conflicting evidence remains fail-closed for reconciliation.
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
})
REMOTE MCP
Call the discovered tool through the JavaScript client.
The server is stateless Streamable HTTP at https://mandateshield.com/api/mcp. It exposes normalize_agent_payment_protocol, check_ai_payment_authority and verify_cryptographic_payment_authority.
Current clients may use the stateless MCP 2026-07-28 discovery and call shape. The same endpoint retains initialize-based compatibility for 2025-11-25, 2025-06-18 and 2025-03-26. Both eras expose the same safety boundary: no PROCESSOR transition, permit redemption or payment execution tool.
import {
createClient,
decisionFromMcp,
requireAllowed
} from "./vendor/mandateshield/mandateshield.mjs"
const client = createClient({
apiKey: process.env.MANDATESHIELD_VERIFY_KEY
})
const result = await client.callMcpTool(
"verify_cryptographic_payment_authority",
{ envelope: purchase, evidence }
)
const decision = decisionFromMcp(result)
const reservation = requireAllowed(decision)
await trustedGateway.enqueueProviderBoundPermitFlow(reservation)
IMPORTANT BOUNDARY
Tool discovery is not enforcement.
Giving a model the MCP tool helps it plan safely, but prompts cannot authorize money movement. MCP provides analysis or a strict verification projection; it exposes no provider-enforcement action and the model must never receive the PROCESSOR key. Only a customer-deployed exclusive executor may consume and redeem before its idempotent provider operation.
Inspect MCP server metadata →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 -> redeem -> provider submission -> hosted reconciliation.
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 ↗