Skip to content

Accept a payment

This is the shortest path from nothing to money arriving in your own wallet. Every request below is plain HTTP, so it works from any language, from curl, and from an agent that can only make web requests.

  • A Jorvi server URL. Everything below uses $JORVI_API for it.
  • A Solana wallet you control, to receive money. Jorvi never holds funds, so payments settle directly from your customer’s wallet to this address.
  1. Create an account

    You get two API keys back. They are shown once and are not recoverable, because only a hash is stored. The response also sets a session cookie, which the next step needs.

    Terminal window
    curl -i -X POST "$JORVI_API/v1/merchants" \
    -H 'content-type: application/json' \
    -d '{
    "name": "Harbour Lane Coffee",
    "email": "you@example.com",
    "password": "a-long-enough-password",
    "acknowledged": true
    }'

    acknowledged is required and must be true. It records that you understand Jorvi never holds your money and cannot reverse a settled payment. Omit it and you get acknowledgement_required.

    Keep three things from the response:

    • keys.test is the one to use. Everything on this page works with it.
    • keys.live is the same account in live mode. On a devnet deployment it settles on the same test chain, so there is no reason to reach for it yet. It exists so the switch to mainnet is a key change rather than a new account.
    • the jorvi_session cookie, which the next step needs. No API key can replace it.
  2. Prove the wallet you want to be paid into

    Anyone can type an address. A typed address is unverified and cannot receive live payments, because a typo would send your revenue somewhere you do not control and nothing on chain can bring it back.

    Ask for a challenge:

    Terminal window
    curl "$JORVI_API/v1/payout_wallet/challenge?address=$WALLET" \
    -H "cookie: jorvi_session=$SESSION"

    Sign the message it returns with that wallet’s key (an ordinary ed25519 signature over the UTF-8 bytes, base64 encoded), then:

    Terminal window
    curl -X POST "$JORVI_API/v1/payout_wallet" \
    -H 'content-type: application/json' \
    -H "cookie: jorvi_session=$SESSION" \
    -d '{"address":"'"$WALLET"'","message":"'"$MESSAGE"'","signature":"'"$SIGNATURE"'"}'

    Nothing moves and no fee is paid. This proves you hold the key, and it is off-chain and free.

  3. Create something to sell

    Terminal window
    curl -X POST "$JORVI_API/v1/products" \
    -H "authorization: Bearer $KEY" \
    -H 'content-type: application/json' \
    -d '{
    "name": "The Coffee Guide",
    "sellMode": "once",
    "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
    "priceOnce": "3000000",
    "periodSeconds": 3600,
    "contentTitle": "Members",
    "contentBody": "The good stuff."
    }'
    • sellMode: "once" sells it outright. Leave it out and the product is a subscription, which this deployment refuses.

    • Prices are strings in atomic units. USDC has six decimals, so "3000000" is 3.00 USDC. Never a float: 3.00 in JSON is a binary fraction and money is not.

    • mint must be a token the platform accepts. Ask for the list with your API key:

      Terminal window
      curl "$JORVI_API/v1/tokens" -H "authorization: Bearer $KEY"

      This route needs the key; without it you get 401 unauthenticated. Anything not on that list is refused with mint_not_allowed.

    • Every field in that example is required, including on a one-time product: name, sellMode, mint, priceOnce, periodSeconds, contentTitle and contentBody. Omit any one and you get parameter_missing naming it.

    • periodSeconds must be a whole number of hours, meaning a multiple of 3600. 1800 and 5400 are both refused with parameter_invalid. It is required on a one-time product even though nothing recurs, which is a wart in the current API; pass 3600 and move on.

    The response carries an id and a url. url is a PATH, not a full address, which is something like /product/prod_a66095…. Join it to your server to get the link you share:

    $JORVI_API + url -> https://pay.example.com/product/prod_a66095…

    That page is a working checkout. Share it and you are selling; there is nothing else to build.

  4. Let someone buy it

    If you are happy with the hosted page, you are finished. To drive it yourself, open a purchase:

    Terminal window
    curl -X POST "$JORVI_API/v1/products/$PRODUCT_ID/buy" \
    -H 'content-type: application/json' \
    -d '{"payer":"'"$PAYER_WALLET"'"}'

    You get back a mandateId, a clientSecret, and an instruction. Despite the name, instruction is a complete unsigned Solana transaction, base64 encoded, not a single instruction within one. Deserialize it, have the payer sign it in their own wallet, and send it back. Only they can sign it, and the platform never holds a key that could.

    Hand the signed transaction back:

    Terminal window
    curl -X POST "$JORVI_API/v1/checkout/mandates/$MANDATE_ID/broadcast?client_secret=$SECRET" \
    -H 'content-type: application/json' \
    -d '{"signedTransaction":"'"$SIGNED"'"}'

    The response carries the txSignature the chain returned. The money has moved: from the payer’s wallet to yours, with no stop in between.

  5. Check what they bought is unlocked

    Terminal window
    curl "$JORVI_API/v1/products/$PRODUCT_ID/access?payer=$PAYER_WALLET&client_secret=$SECRET"

    Returns entitled and, when entitled, the gated contentBody.

    The client_secret is required. Without a proof this route answers 401 wallet_proof_required rather than saying whether that wallet has paid: entitlement is not readable by anyone who can guess an address.

The payer signed one transaction, paying their own network fee, moving their own tokens directly to your wallet. Jorvi built the transaction and broadcast it. At no point did it hold your money, and at no point could it have.

  • Errors, every code the API returns and what to do about each.
  • Webhooks, how to be told a payment happened instead of polling.
  • Integration options: hosted page, embedded button, or driving it yourself, and how to choose.