{"info":{"_postman_id":"69b0cfee-802b-4727-bd7b-b013e89bb889","name":"VERIFIED Crypto Checkout API","description":"<html><head></head><body><p>Official Postman collection for the VERIFIED Crypto Checkout API.</p>\n<h2 id=\"quick-start\">Quick Start</h2>\n<ol>\n<li>Set <code>merchant_wallet</code> to your Polygon USDC wallet address (starts with 0x, 42 chars)</li>\n<li>Set <code>webhook_secret</code> to a strong random string (the SAME value must be set in your webhook handler's environment)</li>\n<li>Set <code>success_url</code> and <code>cancel_url</code> to your redirect URLs</li>\n<li>Run <strong>Create Session (Full)</strong> — test script auto-saves <code>session_id</code> and <code>checkout_url</code></li>\n<li>Redirect your customer to <code>checkout_url</code> exactly as returned — do NOT modify it</li>\n<li>Verify payments via HMAC-SHA256 signed webhooks</li>\n</ol>\n<h2 id=\"key-rules\">Key Rules</h2>\n<ul>\n<li>No API key required — merchant identity = wallet address</li>\n<li><code>order_id</code> is required on every session creation request</li>\n<li><code>webhook_secret</code> is required whenever <code>webhook_url</code> is set</li>\n<li>Sessions expire after 30 minutes</li>\n<li>Never modify <code>checkout_url</code> after receiving it</li>\n<li>Webhooks are the authoritative payment signal — only treat <code>event: payment.confirmed</code> / <code>status: confirmed</code> as paid</li>\n<li>No sandbox — all sessions are live. Use small amounts ($1–$10) for testing.</li>\n</ul>\n<h2 id=\"domains\">Domains</h2>\n<ul>\n<li><strong>API</strong>: <a href=\"https://payapi.verifiedcryptocheckout.com\">https://payapi.verifiedcryptocheckout.com</a></li>\n<li><strong>Hosted Checkout</strong>: <a href=\"https://pay.verifiedcryptocheckout.com\">https://pay.verifiedcryptocheckout.com</a></li>\n</ul>\n<h2 id=\"support\">Support</h2>\n<p><a href=\"mailto:cryptocheckout@verifiedcreditcardprocessing.com\">cryptocheckout@verifiedcreditcardprocessing.com</a></p>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"53498215","collectionId":"69b0cfee-802b-4727-bd7b-b013e89bb889","publishedId":"2sBXinHAaA","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"publishDate":"2026-04-02T00:28:09.000Z"},"item":[{"name":"SESSION CREATION","item":[{"name":"1 · Create Session — Full Request ✅","id":"8bf171e9-932e-4883-a6d0-b30b1f41c937","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","description":"<p>Required on all POST requests.</p>\n"},{"key":"Idempotency-Key","value":"784cc148-2af7-4a6d-842d-8df28452dc5a","description":"<p>Optional but recommended on retries. If same key is reused, the API may return the existing session instead of creating a duplicate.</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 125.50,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"ORDER-1001\",\n  \"customer_email\": \"customer@example.com\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\",\n  \"metadata\": {\n    \"cart_id\": \"CART-8821\",\n    \"customer_id\": \"USR-442\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"8bf171e9-932e-4883-a6d0-b30b1f41c937"},{"name":"2 · Create Session — Minimal (Required Fields Only)","id":"ba24780b-6383-4e8e-8385-78125eaf10db","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 10.00,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"ORDER-001\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"ba24780b-6383-4e8e-8385-78125eaf10db"},{"name":"3 · Create Session — With Provider Hint","id":"9eb1c7f5-742f-45cf-8487-f6d9ed00fe93","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Idempotency-Key","value":"36fc0ee2-9778-4c6c-b656-bcd72386a42a"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 75.00,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"ORDER-PROV-295\",\n  \"customer_email\": \"customer@example.com\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\",\n  \"provider\": \"moonpay\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"9eb1c7f5-742f-45cf-8487-f6d9ed00fe93"}],"id":"14066319-4741-4448-9bc6-65044d2389ae","description":"<p>Create payment sessions — the first and only required step to initiate a checkout flow.</p>\n<p>Your server calls <code>POST /v1/session</code> with the order amount, currency, merchant wallet, and order details. VERIFIED validates the request, configures routing and settlement parameters, and returns a fully prepared <code>checkout_url</code>. Redirect the customer to that URL to begin the payment flow.</p>\n<p>⚠️ Session creation must always be performed server-side — never from client-side JavaScript. Although no API key is required, server-side creation prevents tampering with order amounts, wallet values, and metadata.</p>\n","_postman_id":"14066319-4741-4448-9bc6-65044d2389ae"},{"name":"DEBUG","item":[{"name":"4 · Inspect URL Structure — GET /test-config","id":"83e5ebe2-152d-487e-a421-8bfd1c3f3f4d","request":{"method":"GET","header":[],"url":"https://payapi.verifiedcryptocheckout.com/test-config?wallet=0xYOUR_POLYGON_USDC_WALLET_ADDRESS&amount=10&currency=USD&order_id=TEST-DEBUG-001&customer_email=customer@example.com&success_url=https://yourstore.com/order/success&cancel_url=https://yourstore.com/order/cancelled&webhook_url=https://yourstore.com/api/payment-webhook","urlObject":{"path":["test-config"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[{"key":"wallet","value":"0xYOUR_POLYGON_USDC_WALLET_ADDRESS"},{"key":"amount","value":"10"},{"key":"currency","value":"USD"},{"key":"order_id","value":"TEST-DEBUG-001"},{"key":"customer_email","value":"customer@example.com"},{"key":"success_url","value":"https://yourstore.com/order/success"},{"key":"cancel_url","value":"https://yourstore.com/order/cancelled"},{"key":"webhook_url","value":"https://yourstore.com/api/payment-webhook"}],"variable":[]}},"response":[],"_postman_id":"83e5ebe2-152d-487e-a421-8bfd1c3f3f4d"}],"id":"0c2b9d33-919c-400a-905a-c1453943b6f6","description":"<p>Developer helper endpoint for inspecting generated URL structures without creating a live payment session.</p>\n<p>Use <code>GET /test-config</code> during development to validate your wallet address format, confirm redirect URLs are correctly formed, and inspect the checkout_url structure before going live.</p>\n<p>⚠️ This endpoint does NOT create a live session and does NOT accept real payments.</p>\n","_postman_id":"0c2b9d33-919c-400a-905a-c1453943b6f6"},{"name":"ERROR HANDLING","item":[{"name":"5 · Error — Missing Wallet (expect 400)","id":"1eca86b7-eb8c-4bff-9260-10b30c1fcae8","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 100.00,\n  \"currency\": \"USD\",\n  \"order_id\": \"ORDER-ERROR-TEST\",\n  \"success_url\": \"https://example.com/success\",\n  \"cancel_url\": \"https://example.com/cancel\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"1eca86b7-eb8c-4bff-9260-10b30c1fcae8"},{"name":"6 · Error — Invalid Wallet Format (expect 400)","id":"2528c108-3b35-47b3-b2c8-e485f7870d1b","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 100.00,\n  \"currency\": \"USD\",\n  \"wallet\": \"INVALID_WALLET_NO_0x_PREFIX\",\n  \"order_id\": \"ORDER-WALLET-ERROR\",\n  \"success_url\": \"https://example.com/success\",\n  \"cancel_url\": \"https://example.com/cancel\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"2528c108-3b35-47b3-b2c8-e485f7870d1b"},{"name":"7 · Error — Wrong HTTP Method GET /v1/session (expect 405)","id":"71702ee9-6d0e-42d5-8a23-cc799d501e71","request":{"method":"GET","header":[],"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"71702ee9-6d0e-42d5-8a23-cc799d501e71"},{"name":"8 · Error — Rate Limit Exceeded (expect 429)","id":"382e344c-b12b-3788-fd2d-b3a8d628a24f","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 10.00,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"RATE-LIMIT-TEST\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"382e344c-b12b-3788-fd2d-b3a8d628a24f"}],"id":"75255bb2-c525-4126-b095-df8614a68c29","description":"<p>Reference requests demonstrating common error scenarios and the expected HTTP status codes.</p>\n<p>All VERIFIED API errors return JSON with <code>ok: false</code>, <code>code</code>, <code>error</code>, and <code>message</code> fields.</p>\n","_postman_id":"75255bb2-c525-4126-b095-df8614a68c29"},{"name":"WEBHOOK REFERENCE","item":[{"name":"9 · Webhook Payload — payment.confirmed (Reference)","id":"e2373377-204c-48a5-90da-1a21fb1fe05f","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","description":"<p>Webhook payload is always JSON.</p>\n"},{"key":"X-VCC-Timestamp","value":"1742040731","description":"<p>Unix timestamp (seconds). Reject if &gt;5 minutes from current server time.</p>\n"},{"key":"X-VCC-Signature","value":"a3f8c2e1d7b9f4e2c1a8d7f3b2e9c4a1d8f7e2b3c9a4f1e8d7b2c3f9a4e1d8","description":"<p>HMAC-SHA256(webhook_secret, '{X-VCC-Timestamp}.{raw_body}'). Use constant-time comparison.</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"event\": \"payment.confirmed\",\n  \"session_id\": \"\",\n  \"order_id\": \"ORDER-1001\",\n  \"amount\": 125.50,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"tx_hash\": \"0x8f12c4d9e3b7a2f1c6d8e5b4a3f2e1d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4\",\n  \"status\": \"confirmed\",\n  \"timestamp\": \"2026-03-29T14:32:11Z\",\n  \"amount_forwarded\": 120.48,\n  \"confirmations\": 12,\n  \"raw\": {\n    \"provider\": \"moonpay\",\n    \"provider_tx_id\": \"mp_8472afe1\",\n    \"settlement_chain\": \"polygon\",\n    \"settlement_token\": \"USDC\",\n    \"block_number\": 56234118\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://yourstore.com/api/payment-webhook","urlObject":{"host":["https://yourstore.com/api/payment-webhook"],"query":[],"variable":[]}},"response":[],"_postman_id":"e2373377-204c-48a5-90da-1a21fb1fe05f"}],"id":"b58ab8e3-dc15-427f-b2b6-b62041903146","description":"<p>Reference showing the structure of the HMAC-signed webhook POST that VERIFIED sends to your <code>webhook_url</code> when payment is confirmed on-chain.</p>\n<p>Webhooks are the authoritative payment confirmation signal — never mark an order as paid based on the <code>success_url</code> redirect alone. Verify the HMAC-SHA256 signature on every incoming webhook before updating order state.</p>\n<p>Only treat <code>event: payment.confirmed</code> / <code>status: confirmed</code> as paid. Other event/status combinations (e.g. <code>pending</code>) must not move the order to paid.</p>\n","_postman_id":"b58ab8e3-dc15-427f-b2b6-b62041903146"},{"name":"10 · Payment Links — Sharing checkout_url (Reference)","id":"14040f6b-6659-9c9c-3dbf-51f35c56d07d","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Idempotency-Key","value":"f336ce78-3564-4dda-86d7-a559a4840ad2"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 75.00,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"ORDER-817\",\n  \"customer_email\": \"customer@example.com\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","description":"<p>PATTERN — The <code>checkout_url</code> returned by every session is a fully functional, shareable payment link. Distribute it via email, SMS, invoice PDFs, or dashboard buttons.</p>\n<p>No additional API calls or token generation required. Sessions expire after 30 minutes — create a new session if the link has expired.</p>\n","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"14040f6b-6659-9c9c-3dbf-51f35c56d07d"},{"name":"11 · Abandoned Order Recovery — New Session Pattern (Reference)","id":"4a2233d9-3595-6bbc-284d-794926a9e845","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Idempotency-Key","value":"10d4f8d9-e54d-4b2c-8b0a-a84a04b1237f"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 89.99,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"ORD-RECOVERY-EXAMPLE\",\n  \"customer_email\": \"customer@example.com\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\"\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","description":"<p>PATTERN — Abandoned order recovery for API integrations.</p>\n<p>When a customer creates an order but doesn't complete payment, the session expires after 30 minutes. If no <code>payment.confirmed</code> webhook is received within your recovery window, create a new session for the same order and resend the <code>checkout_url</code> to the customer.</p>\n<p><strong>Suppression guards:</strong></p>\n<ul>\n<li>Order already paid → skip</li>\n<li>Recovery already sent once → skip</li>\n<li>No customer email → skip</li>\n<li>Order older than 72 hours → skip</li>\n</ul>\n","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"4a2233d9-3595-6bbc-284d-794926a9e845"},{"name":"11 · Webhook Recovery — On-Chain Missed Payment Check (Reference)","id":"532bfabb-9b7e-552c-920f-ee6402538972","request":{"method":"GET","header":[],"url":"https://api.polygonscan.com/api?module=account&action=tokentx&contractaddress=0x3c499c542cef5e3811e1192ce70d8cc03d5c3359&address=0xYOUR_POLYGON_USDC_WALLET_ADDRESS&sort=desc&apikey=YOUR_FREE_POLYGONSCAN_KEY","description":"<p>PATTERN REFERENCE — A server-side safety net that confirms payments by reading the Polygon blockchain directly, for cases where a webhook was missed or delayed. This is the direct-API equivalent of the Missed Payment Protection feature built into the VERIFIED WooCommerce plugin.</p>\n<p><strong>Why you need this:</strong> Webhooks are the primary confirmation signal, but no webhook system is perfect — endpoints have downtime, callbacks can be delayed by slow provider KYC, and some payment routes confirm on-chain without an immediate callback. This recovery check guarantees you can confirm any settled payment independently, because USDC settlement on Polygon is public and verifiable.</p>\n<p><strong>Core idea:</strong> Periodically scan your own merchant wallet for incoming USDC transfers that match the expected amount of any order still marked pending. If a match is found, flag the order for confirmation.</p>\n<hr />\n<h2 id=\"design-principles-ported-from-the-production-plugin\">Design principles (ported from the production plugin)</h2>\n<ol>\n<li><strong>Check your OWN merchant wallet.</strong> Funds always land in your destination wallet regardless of how they were routed, so this is the most reliable address to monitor.</li>\n<li><strong>Two query methods, no API key required to start.</strong> Use the public Polygon RPC (<code>eth_getLogs</code>) by default — works with zero setup. Optionally use the Polygonscan API with a free key for higher-volume reliability.</li>\n<li><strong>Match by amount with tolerance.</strong> On-chain amounts rarely match the order total exactly (conversion, rounding). Use a small tolerance (~2%).</li>\n<li><strong>Only check aged, still-pending orders.</strong> Wait at least ~2 hours after order creation so normal webhooks arrive first, then scan on a schedule (e.g. every 3 hours).</li>\n<li><strong>Flag for review — never auto-fulfill blindly.</strong> A matching transfer is strong evidence, but treat it as \"confirm and review,\" not silent auto-completion, to protect against amount mismatches or unrelated deposits.</li>\n</ol>\n<hr />\n<h2 id=\"usdc-contract--transfer-topic-polygon\">USDC contract + Transfer topic (Polygon)</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>USDC (native):  0x3c499c542cef5e3811e1192ce70d8cc03d5c3359\nTransfer topic: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\nPublic RPC:     https://polygon-rpc.com\nDecimals:       6   (so $70.00 = 70000000 raw units)\n</code></pre><hr />\n<h2 id=\"method-a--public-rpc-no-api-key-works-out-of-the-box\">Method A — Public RPC (no API key, works out of the box)</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-javascript\">const USDC = '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359';\nconst TRANSFER_TOPIC =\n  '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';\n\nasync function rpc(method, params) {\n  const res = await fetch('https://polygon-rpc.com', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),\n  });\n  const json = await res.json();\n  if (json.error) throw new Error(JSON.stringify(json.error));\n  return json.result;\n}\n\n// Find incoming USDC transfers to your wallet since a given block.\nasync function findIncomingUSDC(merchantWallet, fromBlock) {\n  const toBlock = await rpc('eth_blockNumber', []);\n  const paddedTo =\n    '0x000000000000000000000000' + merchantWallet.toLowerCase().replace(/^0x/, '');\n\n  const logs = await rpc('eth_getLogs', [{\n    fromBlock: '0x' + Math.max(1, fromBlock).toString(16),\n    toBlock,\n    address: USDC,\n    topics: [TRANSFER_TOPIC, null, paddedTo], // [event, from(any), to(you)]\n  }]);\n\n  return logs.map(log =&gt; ({\n    tx_hash: log.transactionHash,\n    amount: parseInt(log.data, 16) / 1_000_000,        // 6 decimals\n    block: parseInt(log.blockNumber, 16),\n  }));\n}\n</code></pre>\n<hr />\n<h2 id=\"method-b--polygonscan-api-optional-free-key-higher-reliability\">Method B — Polygonscan API (optional, free key, higher reliability)</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-javascript\">async function findIncomingUSDC_Polygonscan(merchantWallet, fromBlock, apiKey) {\n  const url = new URL('https://api.polygonscan.com/api');\n  url.searchParams.set('module', 'account');\n  url.searchParams.set('action', 'tokentx');\n  url.searchParams.set('contractaddress', USDC);\n  url.searchParams.set('address', merchantWallet);\n  url.searchParams.set('startblock', String(Math.max(1, fromBlock)));\n  url.searchParams.set('sort', 'asc');\n  url.searchParams.set('apikey', apiKey);\n\n  const res = await fetch(url);\n  const json = await res.json();\n  if (!Array.isArray(json.result)) return [];\n\n  return json.result\n    .filter(r =&gt; (r.to || '').toLowerCase() === merchantWallet.toLowerCase())\n    .map(r =&gt; ({\n      tx_hash: r.hash,\n      amount: Number(r.value) / 1_000_000,\n      block: Number(r.blockNumber),\n    }));\n}\n</code></pre>\n<hr />\n<h2 id=\"amount-matching-with-tolerance\">Amount matching with tolerance</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-javascript\">function pickMatch(events, expectedAmount) {\n  if (!events.length) return null;\n  const tolerance = Math.max(0.01, expectedAmount * 0.02); // 2%\n  let best = null, bestDiff = null;\n  for (const e of events) {\n    const diff = Math.abs(e.amount - expectedAmount);\n    if (diff &lt;= tolerance &amp;&amp; (bestDiff === null || diff &lt; bestDiff)) {\n      best = e; bestDiff = diff;\n    }\n  }\n  return best;\n}\n</code></pre>\n<hr />\n<h2 id=\"the-scheduled-recovery-scan-run-every-3-hours\">The scheduled recovery scan (run every ~3 hours)</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-javascript\">const POLYGON_BLOCK_SECONDS = 2;\n\nasync function runRecoveryScan() {\n  // Orders still pending and older than the min age (e.g. 2 hours)\n  const minAgeMs = 2 * 60 * 60 * 1000;\n  const stuck = await db.orders.find({\n    status: 'pending',\n    created_at: { $lt: new Date(Date.now() - minAgeMs) },\n    recovery_checked: { $ne: true },\n  });\n\n  const currentBlockHex = await rpc('eth_blockNumber', []);\n  const currentBlock = parseInt(currentBlockHex, 16);\n\n  for (const order of stuck) {\n    // Narrow the block range from order age (+buffer)\n    const ageSec = Math.floor((Date.now() - new Date(order.created_at)) / 1000);\n    const fromBlock = Math.max(\n      1,\n      currentBlock - Math.ceil(ageSec / POLYGON_BLOCK_SECONDS) - 5000\n    );\n\n    let events;\n    try {\n      events = process.env.POLYGONSCAN_API_KEY\n        ? await findIncomingUSDC_Polygonscan(\n            process.env.MERCHANT_WALLET, fromBlock, process.env.POLYGONSCAN_API_KEY)\n        : await findIncomingUSDC(process.env.MERCHANT_WALLET, fromBlock);\n    } catch (e) {\n      // Retry up to 3 times across future scans, then give up gracefully\n      const retries = (order.recovery_retries || 0) + 1;\n      await db.orders.update({ id: order.id }, { recovery_retries: retries });\n      if (retries &gt;= 3) {\n        await db.orders.update({ id: order.id }, { recovery_checked: true });\n        await alertOps(order, 'Recovery check failed after 3 attempts — review manually');\n      }\n      continue;\n    }\n\n    const match = pickMatch(events, order.expected_amount);\n    await db.orders.update({ id: order.id }, { recovery_checked: true });\n\n    if (match) {\n      // Flag for confirmation — do NOT silently auto-fulfil\n      await db.orders.update({ id: order.id }, {\n        status: 'paid_pending_review',\n        tx_hash: match.tx_hash,\n        on_chain_amount: match.amount,\n        on_chain_block: match.block,\n      });\n      await alertOps(order, `On-chain USDC match found: ${match.amount} USDC, tx ${match.tx_hash}`);\n    }\n  }\n}\n</code></pre>\n<hr />\n<h2 id=\"notes\">Notes</h2>\n<ul>\n<li><strong>No API key path works immediately</strong> via <code>polygon-rpc.com</code>. Add a free Polygonscan key only if you run high volume and want extra reliability.</li>\n<li><strong><code>expected_amount</code></strong> should be the USDC amount you expect to receive. If you pass non-USD currency at session creation it is converted to USD/USDC, so store the converted figure where possible.</li>\n<li><strong>Verify on Polygonscan</strong> any <code>tx_hash</code> at <a href=\"https://polygonscan.com/tx/%7Btx_hash%7D\">https://polygonscan.com/tx/{tx_hash}</a> for an independent audit trail.</li>\n<li>This recovery check complements webhooks — it does not replace them. Keep your signed webhook handler as the primary path and run this scan as the safety net.</li>\n</ul>\n","urlObject":{"protocol":"https","path":["api"],"host":["api","polygonscan","com"],"query":[{"key":"module","value":"account"},{"key":"action","value":"tokentx"},{"description":{"content":"<p>Native USDC on Polygon (Circle-issued). Chain ID 137.</p>\n","type":"text/plain"},"key":"contractaddress","value":"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"},{"key":"address","value":"0xYOUR_POLYGON_USDC_WALLET_ADDRESS"},{"key":"sort","value":"desc"},{"key":"apikey","value":"YOUR_FREE_POLYGONSCAN_KEY"}],"variable":[]}},"response":[],"_postman_id":"532bfabb-9b7e-552c-920f-ee6402538972"},{"name":"13 · Recurring Payments — Session Per Billing Cycle (Reference)","id":"a8e8549b-fa5a-e3a0-d5a7-1711639ebdb4","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"},{"key":"Idempotency-Key","value":"25d89396-2703-4dfb-a0c5-8a71c3b40fc6"}],"body":{"mode":"raw","raw":"{\n  \"amount\": 29.99,\n  \"currency\": \"USD\",\n  \"wallet\": \"0xYOUR_POLYGON_USDC_WALLET_ADDRESS\",\n  \"order_id\": \"RNW-SUB001-334\",\n  \"customer_email\": \"customer@example.com\",\n  \"success_url\": \"https://yourstore.com/order/success\",\n  \"cancel_url\": \"https://yourstore.com/order/cancelled\",\n  \"webhook_url\": \"https://yourstore.com/api/payment-webhook\",\n  \"webhook_secret\": \"CHANGE_ME_TO_A_STRONG_RANDOM_STRING\",\n  \"metadata\": {\n    \"subscription_id\": \"SUB-001\",\n    \"billing_cycle\": \"2026-04\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://payapi.verifiedcryptocheckout.com/v1/session","description":"<p>PATTERN — Recurring payments via session-per-billing-cycle.</p>\n<p>VERIFIED does not manage subscription state. Each billing cycle, your system creates a new payment session for the renewal amount, sends the <code>checkout_url</code> to the customer, and listens for the <code>payment.confirmed</code> webhook. This pattern works with any billing platform.</p>\n<p><strong>Key notes:</strong></p>\n<ul>\n<li>Each renewal is a completely independent session</li>\n<li><code>order_id</code> is required — use a unique value per renewal attempt (include a timestamp)</li>\n<li>You are responsible for tracking subscription state and retry logic in your own system</li>\n</ul>\n","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"a8e8549b-fa5a-e3a0-d5a7-1711639ebdb4"},{"name":"ℹ️ FAQ — Common Integration Questions (Reference)","id":"bc1ce4ec-4dbe-47c9-9a01-2eba2cfb6641","request":{"method":"GET","header":[],"url":"https://payapi.verifiedcryptocheckout.com/v1/session","description":"<p>REFERENCE — Do not send. Answers to common integration questions.</p>\n<h2 id=\"q1--webhook-signing-secret-how-to-obtain-and-rotate\">Q1 — Webhook signing secret: how to obtain and rotate?</h2>\n<p><strong>You define it.</strong> Pass <code>webhook_secret</code> in the session creation body. <strong>It is required whenever <code>webhook_url</code> is set</strong> — an endpoint that verifies signatures will reject (401) an unsigned delivery. VERIFIED uses it to sign outbound webhooks via HMAC-SHA256. To rotate: supply a new value in the next session. Each session carries its own secret independently.</p>\n<h2 id=\"q2--is-there-a-get-endpoint-to-query-session-status\">Q2 — Is there a GET endpoint to query session status?</h2>\n<p><strong>No.</strong> Session state is communicated exclusively via webhook callbacks. If a webhook is missed after 2+ hours, use the Polygonscan on-chain fallback.</p>\n<h2 id=\"q3--webhook-payload-fields\">Q3 — Webhook payload fields</h2>\n<p><strong>9 stable core fields</strong> always present: <code>event</code>, <code>session_id</code>, <code>order_id</code>, <code>amount</code>, <code>currency</code>, <code>wallet</code>, <code>tx_hash</code>, <code>status</code>, <code>timestamp</code>.</p>\n<p><strong>Additional non-breaking fields</strong> also delivered: <code>amount_forwarded</code> (net forwarded after fees), <code>confirmations</code> (on-chain confirmations), <code>raw</code> (full upstream provider passthrough).</p>\n<p>Treat unknown keys as non-breaking and ignore them. Build business logic against the 9 core fields.</p>\n<h2 id=\"q4--event--status-semantics\">Q4 — Event / status semantics</h2>\n<p>Only treat <code>event: payment.confirmed</code> AND <code>status: confirmed</code> as paid. If a <code>payment.pending</code> / <code>status: pending</code> event ever arrives, do not mark the order paid — wait for the <code>confirmed</code> delivery.</p>\n<h2 id=\"q5--multi-chain--multi-token-settlements\">Q5 — Multi-chain / multi-token settlements</h2>\n<p>The webhook fires reliably regardless of settlement chain/token. Payload reflects actual chain and token used. For high-value transactions, verify <code>tx_hash</code> on polygonscan.com.</p>\n<h2 id=\"q6--provider-failover\">Q6 — Provider failover</h2>\n<p>Handled automatically by VERIFIED routing. No API parameter to configure fallback order. Omit <code>provider</code> for automatic routing.</p>\n<h2 id=\"q7--fee-structure\">Q7 — Fee structure</h2>\n<ul>\n<li>Platform fee (4%): deducted from USDC settlement</li>\n<li>Upstream onramp fee (~4%): deducted from card charge before USDC conversion</li>\n<li>Merchant receives USDC net of both</li>\n<li><code>amount_forwarded</code> in the webhook reports the net delivered to your wallet</li>\n</ul>\n<h2 id=\"q8--settlement-timing\">Q8 — Settlement timing</h2>\n<p>~2–5 minutes end-to-end from card approval to webhook fire. Webhook and USDC arrival are simultaneous (same on-chain event).</p>\n<h2 id=\"q9--rate-limits\">Q9 — Rate limits</h2>\n<p>~1 req/sec/IP standard. On 429, back off using <code>Retry-After</code> header. Contact support for enterprise limits.</p>\n","urlObject":{"path":["v1","session"],"host":["https://payapi.verifiedcryptocheckout.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"bc1ce4ec-4dbe-47c9-9a01-2eba2cfb6641"}],"event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["// Collection-level: validate wallet format before any request","const wallet = pm.collectionVariables.get('merchant_wallet');","if (wallet && wallet !== '0xYOUR_POLYGON_USDC_WALLET_ADDRESS') {","  if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) {","    console.warn('⚠️  merchant_wallet format invalid. Expected: 0x + 40 hex chars (42 total). Got: ' + wallet);","  }","}"],"id":"8ef38d27-601f-4eb9-89ff-20fe9a3937f9"}}],"variable":[{"key":"base_url","value":"https://payapi.verifiedcryptocheckout.com","description":"VERIFIED API base URL. Do not change."},{"key":"merchant_wallet","value":"0xYOUR_POLYGON_USDC_WALLET_ADDRESS","description":"Your Polygon USDC wallet address. Must start with 0x and be exactly 42 characters (0x + 40 hex). This is your merchant identifier — USDC settles here. USDC sent to an incorrect wallet cannot be recovered."},{"key":"webhook_secret","value":"CHANGE_ME_TO_A_STRONG_RANDOM_STRING","description":"Your webhook signing secret. REQUIRED whenever webhook_url is set. You define this value. VERIFIED uses it to compute HMAC-SHA256 signatures on outbound webhook callbacks. The EXACT same value must be set as VCC_WEBHOOK_SECRET (or equivalent) in your webhook handler's environment so signatures can be verified. An endpoint that verifies signatures will reject (401) an unsigned delivery. Rotate by supplying a new value in the next session create. Never commit to source control."},{"key":"order_id","value":"ORDER-1001","description":"Your unique internal order reference. REQUIRED on every session creation request. Returned in all webhook events for matching. Must be unique per session."},{"key":"customer_email","value":"customer@example.com","description":"Customer email — used by the provider for KYC and transaction confirmation."},{"key":"success_url","value":"https://yourstore.com/order/success","description":"URL to redirect customer after successful payment. Best-effort only — always use webhook as authoritative confirmation."},{"key":"cancel_url","value":"https://yourstore.com/order/cancelled","description":"URL to redirect customer if they cancel or abandon checkout."},{"key":"webhook_url","value":"https://yourstore.com/api/payment-webhook","description":"Your HTTPS endpoint to receive HMAC-SHA256 signed payment confirmation events. Strongly recommended for production. If set, webhook_secret is required."},{"key":"session_id","value":"","description":"Auto-populated by test script after session creation. Store this on your order record to match incoming webhook events."},{"key":"checkout_url","value":"","description":"Auto-populated by test script. Redirect your customer to this URL exactly as returned — do NOT modify, decode, or append parameters."}]}