feat/agentic-commerce-x402-local-sim
mdx 248 lines 9.36 KB
Raw
1 ---
2 title: "How to Authenticate Every Request to ProcureNet APIs"
3 sidebarTitle: "Authentication"
4 description: "ProcureNet uses four credential types — mod keys, JWT tokens, webhook HMAC signatures, and admin keys — each scoped to a specific part of the API."
5 ---
6
7 ProcureNet authenticates requests through four distinct mechanisms, each scoped to a different surface of the API. Wallet and payment endpoints use a mod key passed as a custom header. Session endpoints require a JWT bearer token. Inbound webhooks from Procurement Express carry an HMAC signature you must verify before processing. Admin override endpoints require a separate admin key. Understanding which credential applies where prevents auth failures and keeps your integration secure.
8
9 ---
10
11 ## API Keys (Wallet and Payment Endpoints)
12
13 Wallet and payment endpoints authenticate with a **mod key** passed in the `x-perplexity-mod-key` header. You receive this key from the ProcureNet dashboard when you create a workspace. Use it on all requests to the USDC payment bridge:
14
15 - `POST /mods/procurement_wallet/usdc/request` — create a USDC payment request
16 - `GET /mods/procurement_wallet/usdc/watch/{paymentId}` — poll for payment confirmation
17
18 ```typescript
19 const response = await fetch(
20 `${process.env.PROCURENET_BASE_URL}/mods/procurement_wallet/usdc/request`,
21 {
22 method: "POST",
23 headers: {
24 "Content-Type": "application/json",
25 "x-perplexity-mod-key": process.env.PROCURENET_MOD_KEY!,
26 },
27 body: JSON.stringify({
28 tenant_id: "your-tenant-id",
29 amount_usdc: 250,
30 network: "base",
31 token: "USDC",
32 memo: "Evaluation payout — Assignment #A-1042",
33 metadata: {
34 evaluator_id: "eval_0192XZ",
35 assignment_id: "A-1042",
36 },
37 }),
38 }
39 );
40
41 const payment = await response.json();
42 // { payment_id, deposit_address, qr_code, expires_at }
43 ```
44
45 <Note>
46 Your mod key carries full access to the wallet API. Never embed it in client-side code, browser bundles, or public repositories. Always load it from a server-side environment variable.
47 </Note>
48
49 ---
50
51 ## JWT Bearer Tokens (Session Endpoints)
52
53 Session endpoints require a **JWT bearer token** in the standard `Authorization` header. You receive a JWT after authenticating with ProcureNet's identity service (consult your onboarding docs for the token exchange flow specific to your setup). Pass this token on every request to the following endpoints:
54
55 | Endpoint | Method | Purpose |
56 |---|---|---|
57 | `/api/v7/sessions/{id}/prefill` | GET | Retrieve prefill data for resolved sessions |
58 | `/api/v7/sessions/{id}/transition` | POST | Advance the session state machine |
59 | `/api/v7/sessions/{id}/escrow-accept` | POST | Accept escrow for high-value transactions |
60 | `/api/v7/sessions/{id}/funding-method` | POST | Set the payment method on a session |
61
62 ```typescript
63 const sessionId = "sess_01JKAB3MXPQ7RVTZWN5";
64
65 // Retrieve prefill data (resolved sessions only — WCS ≥ 100)
66 const prefillResponse = await fetch(
67 `${process.env.PROCURENET_BASE_URL}/api/v7/sessions/${sessionId}/prefill`,
68 {
69 method: "GET",
70 headers: {
71 Authorization: `Bearer ${process.env.PROCURENET_JWT}`,
72 },
73 }
74 );
75
76 const prefill = await prefillResponse.json();
77 // { customer: { ... }, payment_preferences: [...] }
78 ```
79
80 ```typescript
81 // Set a funding method on the session
82 const fundingResponse = await fetch(
83 `${process.env.PROCURENET_BASE_URL}/api/v7/sessions/${sessionId}/funding-method`,
84 {
85 method: "POST",
86 headers: {
87 "Content-Type": "application/json",
88 Authorization: `Bearer ${process.env.PROCURENET_JWT}`,
89 },
90 body: JSON.stringify({
91 method: "card",
92 token: "pm_card_visa_deferred",
93 }),
94 }
95 );
96 ```
97
98 <Tip>
99 JWTs are scoped to the session they were issued for. Passing a token from session A on a request for session B returns a `403 Forbidden` response.
100 </Tip>
101
102 ---
103
104 ## Webhook Signature Verification
105
106 When Procurement Express sends an approval callback to your `POST /webhooks/procurement` endpoint, it includes an HMAC signature in the `x-procure-signature` header. You **must** verify this signature before processing the payload — unverified webhooks can be spoofed by any party that knows your endpoint URL.
107
108 Verification works by computing an HMAC-SHA256 digest of the raw request body using your webhook secret, then comparing it to the value in `x-procure-signature`. Use a constant-time comparison to avoid timing attacks.
109
110 ```typescript
111 import { createHmac, timingSafeEqual } from "crypto";
112 import type { IncomingMessage, ServerResponse } from "http";
113
114 function verifyProcureSignature(
115 rawBody: Buffer,
116 signatureHeader: string,
117 secret: string
118 ): boolean {
119 const expected = createHmac("sha256", secret)
120 .update(rawBody)
121 .digest("hex");
122
123 const expectedBuf = Buffer.from(expected, "utf8");
124 const receivedBuf = Buffer.from(signatureHeader, "utf8");
125
126 // Buffers must be the same length before timingSafeEqual
127 if (expectedBuf.length !== receivedBuf.length) {
128 return false;
129 }
130
131 return timingSafeEqual(expectedBuf, receivedBuf);
132 }
133
134 // Example Express handler
135 app.post(
136 "/webhooks/procurement",
137 express.raw({ type: "application/json" }),
138 (req: IncomingMessage & { body: Buffer }, res: ServerResponse) => {
139 const signature = req.headers["x-procure-signature"] as string;
140
141 if (!signature) {
142 res.writeHead(400);
143 res.end("Missing signature");
144 return;
145 }
146
147 const isValid = verifyProcureSignature(
148 req.body,
149 signature,
150 process.env.PROCURENET_WEBHOOK_SECRET!
151 );
152
153 if (!isValid) {
154 res.writeHead(401);
155 res.end("Invalid signature");
156 return;
157 }
158
159 const payload = JSON.parse(req.body.toString());
160 // payload: { session_id, decision: "approved" | "rejected", actor, reason? }
161 console.log("Procurement approval received:", payload);
162
163 res.writeHead(204);
164 res.end();
165 }
166 );
167 ```
168
169 <Warning>
170 Always read the raw request body **before** any JSON parsing middleware runs. If your framework parses the body into an object first, re-serializing it can change whitespace and break the HMAC digest, causing all verification checks to fail.
171 </Warning>
172
173 ---
174
175 ## Admin Keys
176
177 The `x-admin-key` header is required for manual payment confirmation at `POST /mods/procurement_wallet/usdc/manual-confirm`. This endpoint exists for operational overrides — for example, when an on-chain confirmation is delayed and you need to unblock a field evaluator's payout manually.
178
179 Pass both your mod key and your admin key together on this request:
180
181 ```typescript
182 const confirmResponse = await fetch(
183 `${process.env.PROCURENET_BASE_URL}/mods/procurement_wallet/usdc/manual-confirm`,
184 {
185 method: "POST",
186 headers: {
187 "Content-Type": "application/json",
188 "x-perplexity-mod-key": process.env.PROCURENET_MOD_KEY!,
189 "x-admin-key": process.env.PROCURENET_ADMIN_KEY!,
190 },
191 body: JSON.stringify({
192 payment_id: "pay_01HXYZ77BBQRST",
193 }),
194 }
195 );
196
197 const result = await confirmResponse.json();
198 // { confirmed: true, transaction_hash: "0xabc..." }
199 ```
200
201 <Warning>
202 Admin keys grant override authority over payment confirmation. Treat them with the same security rigor as private keys — restrict access to server-side infrastructure only, rotate them immediately if exposed, and audit their use through your logging pipeline.
203 </Warning>
204
205 ---
206
207 ## Error Reference
208
209 When authentication fails, ProcureNet returns one of the following HTTP status codes:
210
211 | Status | Meaning | Common Causes |
212 |---|---|---|
213 | `401 Unauthorized` | Missing or invalid credential | No `Authorization` header, expired JWT, malformed mod key, failed HMAC verification |
214 | `403 Forbidden` | Valid credential but insufficient access | JWT scoped to a different session, prefill requested on a non-resolved session (WCS &lt; 100), admin endpoint called without `x-admin-key` |
215 | `429 Too Many Requests` | Rate limit exceeded | Too many requests in a short window; back off and retry with exponential delay |
216
217 <Accordion title="Handling 401 vs 403 in your client">
218 A `401` means the credential itself is unrecognized or malformed — check that you're sending the right header name, that the token hasn't expired, and that you haven't accidentally URL-encoded the value.
219
220 A `403` means ProcureNet recognized your credential but the specific action is not permitted. The most common cause is calling `GET /api/v7/sessions/{id}/prefill` on a session with a WCS score below 100. Check the `mode` field from your original orchestration response before attempting prefill.
221 </Accordion>
222
223 ---
224
225 ## Credential Summary
226
227 <CardGroup cols={2}>
228 <Card title="x-perplexity-mod-key" icon="key">
229 **Scope:** Wallet and USDC payment endpoints
230 **Where to get it:** ProcureNet dashboard
231 **Never expose:** client-side code or public repos
232 </Card>
233 <Card title="Authorization: Bearer JWT" icon="id-badge">
234 **Scope:** All `/api/v7/sessions/{id}/...` endpoints
235 **Where to get it:** ProcureNet identity service token exchange
236 **Scoped to:** individual sessions
237 </Card>
238 <Card title="x-procure-signature" icon="shield-check">
239 **Scope:** Inbound webhook verification
240 **Where to get it:** ProcureNet dashboard (webhook secret)
241 **Direction:** Inbound — you verify it, not send it
242 </Card>
243 <Card title="x-admin-key" icon="shield-halved">
244 **Scope:** Manual payment confirmation only
245 **Where to get it:** ProcureNet account team
246 **Never expose:** client-side code, logs, or public repos
247 </Card>
248 </CardGroup>