{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/x402-payments-overview-04a296ff/node-sdk-x402-paying","markdown_url":"https://test.abhinandan.one/x402-payments-overview-04a296ff/node-sdk-x402-paying.md","article":{"id":"ea66f94f-bca4-46f4-9ca0-e1128076e71c","article_slug":"node-sdk-x402-paying","parent_article_slug":"x402-payments-overview-04a296ff","parent_article_title":"x402 Payments Overview","kind":"guide","published_at":"2026-08-11T18:54:55.97204+00:00","keywords":["x402.pay()","createX402Client","viem LocalAccount","X402Error","settle_tx","getChallenge"],"meta_description":"Call x402.pay(challenge, { signer }) with a viem LocalAccount to sign and settle an x402 payment challenge and read the settlement receipt.","og_image_url":null,"source_file_paths":["sdk-node/README.md","sdk-node/src/x402/client.ts","sdk-go/x402.go","sdk-go/x402_test.go","sdk-go/README.md"],"recording_id":null,"replayable":false,"task_name":"Paying a Challenge","category":"Node.js SDK","summary":null,"description":"Sign and submit an x402 payment challenge with pay() using a viem LocalAccount, then read the settlement receipt returned by the platform.","content_kind":"repo_page","content_markdown":"As the payer, you sign an [x402 payment challenge](x402-payments-overview) with your own wallet key and submit it for settlement with a single `pay()` call. The signing is local and non-custodial: the key never leaves your process.\n\nFor payment challenges delivered over an email thread instead of a synthetic id, use [email-native x402 payments](node-sdk-x402-email) instead, `pay()` here is for the out-of-band challenge flow from [charging and registering payout addresses](node-sdk-x402-charging).\n\n<Tip>\n\nPrefer `pay()` over hand-rolling the signature. Drop to the [low-level signing primitives](node-sdk-x402-signing-primitives) only when `pay()` doesn't fit your signing flow (hardware wallet, custom submission path).\n\n</Tip>\n\n## Prerequisites\n\nYou need the SDK, a challenge object from the payee, a payer private key in the environment, and `viem` for the signer.\n\n- `@primitivedotdev/sdk` installed, per the [Node.js SDK quickstart](node-sdk-quickstart).\n- A payment challenge object, obtained from the payee's `charge()` call (see [charging and registering payout addresses](node-sdk-x402-charging)).\n- A payer wallet private key, set as `PAYER_KEY` in your environment.\n- `viem` installed (`npm install viem`) for `privateKeyToAccount`.\n\n## Sign and submit the payment\n\n<Steps>\n\n<Step title=\"Construct the x402 client\">\n\nImport `createX402Client` from the `x402` subpath (or call `primitive.x402(...)` from the root import):\n\n```typescript\n// pay.ts\nimport { createX402Client } from \"@primitivedotdev/sdk/x402\";\n\nconst x402 = createX402Client({ apiKey: process.env.PRIMITIVE_API_KEY! });\n```\n\nEach request has a 30000 ms timeout by default; pass `timeoutMs` to `createX402Client` to change it.\n\n</Step>\n\n<Step title=\"Build a signer from your private key\">\n\nUse viem's `privateKeyToAccount` to turn `PAYER_KEY` into a `LocalAccount`. `pay()` calls its `signTypedData` method; the key stays in your process the whole time.\n\n```typescript\n// pay.ts (continued)\nimport { privateKeyToAccount } from \"viem/accounts\";\n\nconst payer = privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`);\n```\n\n</Step>\n\n<Step title=\"Call pay() with the challenge and signer\">\n\n`challenge` is the object the payee handed you (over email, an API call, or any out-of-band channel). Pass it straight to `pay()`:\n\n```typescript\n// pay.ts (continued)\nconst receipt = await x402.pay(challenge, { signer: payer });\n\nconsole.log(receipt.status, receipt.settle_tx); // \"settled\", \"0x...\" on-chain tx hash\n```\n\n`pay()` derives the interaction-bound EIP-3009 nonce, assembles the `TransferWithAuthorization` payload, signs it with `payer.signTypedData`, and submits it to the platform. The platform verifies every signed field against its own records, checks the org's [spend policy](node-sdk-x402-spend-policy) if applicable, and settles on chain.\n\n</Step>\n\n</Steps>\n\n**Verification signal**: `receipt.status` reads `\"settled\"` and `receipt.settle_tx` carries a non-null on-chain transaction hash.\n\n## Re-hydrating a challenge to retry\n\nCall `getChallenge(id)` to re-fetch a challenge by id, for example after a process restart, instead of re-requesting it from the payee.\n\n```typescript\n// retry.ts (x402 and payer constructed as above)\nconst challenge = await x402.getChallenge(challengeId);\nconst receipt = await x402.pay(challenge, { signer: payer });\n```\n\n## Handling errors\n\n`pay()` throws `X402Error` on any client-side, transport, or non-2xx server error, carrying the HTTP status, the parsed error body, and any `Retry-After` value.\n\n| Field | Meaning |\n| --- | --- |\n| `status` | HTTP status number, or `0` for a client-side, transport, or timeout error that never reached the server |\n| `body` | The parsed error envelope, when present |\n| `retryAfter` | The `Retry-After` response header as a string, or `null` when the server sent none |\n\n```typescript\n// pay.ts (continued)\nimport { X402Error } from \"@primitivedotdev/sdk/x402\";\n\ntry {\n  const receipt = await x402.pay(challenge, { signer: payer });\n} catch (err) {\n  if (err instanceof X402Error) {\n    console.error(err.status, err.message, err.body);\n  }\n}\n```\n\n<Warning>\n\nA `status === 0` error on `pay()` means the request may not have been sent at all, the payment outcome is indeterminate. Don't assume the payment failed; check `getChallenge(id)` or the org's payment history before retrying, since retrying a payment that actually went through is not idempotent in the way `charge()` is.\n\n</Warning>\n\nFor the full error catalog, see [Node.js SDK Errors](node-sdk-errors).\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Charging and Registering Payout Addresses\" href=\"node-sdk-x402-charging\">\n\nCreate the challenge this page pays, as the payee.\n\n</Card>\n\n<Card title=\"Email-Native x402 Payments\" href=\"node-sdk-x402-email\">\n\nPay a challenge that rides a real email thread instead of a synthetic id.\n\n</Card>\n\n<Card title=\"Low-Level x402 Signing Primitives\" href=\"node-sdk-x402-signing-primitives\">\n\nDrive nonce derivation and signing yourself when pay() doesn't fit your flow.\n\n</Card>\n\n<Card title=\"Node.js SDK Errors\" href=\"node-sdk-errors\">\n\nLook up X402Error and every other error type the SDK raises.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-node/README.md","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Paying+a+Challenge&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fnode-sdk-x402-paying","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}