Choose the correct recipe contract
Classic sale/refund and REST recurring examples are different protocols.
Classic sale and refund
The source sale/refund recipes call /api/transact.php with form encoding and security_key in the body. They do not use the REST Authorization header. Use type=sale with payment_token and amount, or type=refund with the original transactionid and refund amount. # Server-side Classic request; placeholders are not live credentials. curl --request POST 'https://sandbox.nmi.com/api/transact.php' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode "security_key=$NMI_PRIVATE_KEY" \ --data-urlencode 'type=refund' \ --data-urlencode 'transactionid=REPLACE_WITH_ORIGINAL_TRANSACTION_ID' \ --data-urlencode 'amount=1.00'
Decode the real response
The recipes show {success:true} as an example, but Classic gateway replies use transaction-result fields. Treat success as your application’s normalized response only. The source sale cURL also has broken continuation lines; the corrected request above is not a literal copy. Payment outcome codes Refund recipe
REST recurring
The recurring recipe calls /api/v5/subscriptions with JSON and Authorization. Its plan_details example combines plan_id and plan fields and uses a historical start date. Choose the documented existing-plan or inline-plan contract rather than assuming every sample field belongs together. Subscription reference Recurring recipe
Frontend handoff
The source uses NmiPayments from @nmipayments/nmi-pay-react or mountNmiPayments from @nmipayments/nmi-pay. onPay sends a token to your server and returns true or an error message. Derive amount from the server order, not an editable browser field. A frontend {success:true} response must reflect a verified backend outcome. Frontend recipe Server sale recipe