Skip to content
NervixPayDocs

Link a payment to a customer account

For subscriptions and memberships: confirmation codes, the signed Continue link, POST /api/v1/payments/claim and the SDK claimPayment helper.

Use this when a payment must land on one specific signed-in user, even if they paid from a phone or a different browser. The customer gets a confirmation code by email and your backend claims it once.

  1. Step 1: Turn on "Requires account link"

    Tick Requires account link on the payment link or embed (or send "requiresCustomerLink": true to the API). The checkout then asks for the customer's email before showing the payment.

  2. Step 2: The customer pays and receives a code

    After the payment is final, the receipt email contains an eight-character confirmation code (no 0, O, 1 or I) and, when the checkout was opened from your website, a Continue button back to it: https://<your site>/?nervixpay_code=<code>&nervixpay_sig=<signature>.

  3. Step 3: Claim the code from your backend

    Call POST /api/v1/payments/claim with a key that has payments:read. Send the signature too when the customer arrived through the Continue link: it proves the link was not edited.

    claim-nervixpay.mjs
    const NERVIXPAY_ORIGIN = 'https://nervixpay.live'
    
    /** Call from your backend after the customer lands on ?nervixpay_code=...&nervixpay_sig=... or types a code. */
    export async function claimNervixPayCode(code, signature, token = process.env.NERVIXPAY_TOKEN) {
      const response = await fetch(NERVIXPAY_ORIGIN + '/api/v1/payments/claim', {
        method: 'POST',
        headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' },
        body: JSON.stringify(signature ? { code, signature } : { code }),
      })
      const body = await response.json().catch(() => null)
      if (response.ok) return { ok: true, payment: body.payment, claimedAt: body.claimedAt, linkVerified: body.linkVerified }
      return { ok: false, status: response.status, code: body?.error?.code ?? 'request_failed', alreadyClaimedAt: body?.alreadyClaimedAt }
    }
    
    Continue link route (Express)
    import { claimNervixPayCode } from './claim-nervixpay.mjs'
    
    // The receipt's "Continue" button opens https://<your site>/?nervixpay_code=ABCD2345&nervixpay_sig=<hex>
    app.get('/', requireSignedInUser, async (req, res, next) => {
      const code = req.query.nervixpay_code
      if (!code) return next()
      const result = await claimNervixPayCode(String(code), req.query.nervixpay_sig ? String(req.query.nervixpay_sig) : undefined)
      if (result.ok) {
        await grantAccess(req.user.id, result.payment) // your logic: amountReceived, itemName, paidAt...
        return res.redirect('/account?upgraded=1')
      }
      if (result.code === 'already_claimed') return res.redirect('/account?already-linked=1')
      res.status(result.status).send('This confirmation code could not be linked.')
    })
    
    Successful claim
    {
      "payment": {
        "id": "22222222-2222-4222-8222-222222222222",
        "itemName": "Pro plan",
        "network": "polygon",
        "currencyToken": "USDC",
        "amountReceived": "25.000123",
        "customerEmail": "buyer@example.com",
        "customerReference": null,
        "originSite": "shop.example.com",
        "paidAt": "2026-09-26T10:02:10.000Z",
        "transactionId": "0xabc0000000000000000000000000000000000000000000000000000000000001",
        "transactionUrl": "https://polygonscan.com/tx/0xabc0000000000000000000000000000000000000000000000000000000000001"
      },
      "claimedAt": "2026-09-26T12:00:00.000Z",
      "linkVerified": true
    }
  4. Step 4: Offer a "Have a code?" field

    Customers who open the email on another device can type the code into your account page. Claim it the same way, without a signature.

Responses

StatusCodeMeaning
200Claimed now. Credit payment to the signed-in user.
404not_foundNo paid order of yours carries this code.
409already_claimedClaimed before, at alreadyClaimedAt. Treat it as a replay unless it was your own retry.
400invalid_signatureA signature was sent and does not match the code and site.

linkVerified is true or false when a signature was checked and null when none was sent. The claim itself is always bound to your key and consumed once.

Coming soon: the integration kit (@nervixpay/integration) is not on npm yet. Once published, claimPayment and continueLinkParams do the same as the code above:

Integration kit
import { claimPayment, continueLinkParams } from '@nervixpay/integration'

const link = continueLinkParams(request.url) // { code, signature } or null
if (link) {
  const result = await claimPayment(link.code, {
    baseUrl: 'https://nervixpay.live',
    accessToken: process.env.NERVIXPAY_TOKEN,
    signature: link.signature ?? undefined,
  })
  if (result.ok) await grantAccess(currentUser.id, result.payment)
}
Link a payment to a customer account | NervixPay Docs