NMI has published a Classic API Migration Playbook that walks merchants and software vendors through moving server-side payment calls off the gateway’s /api/transact.php endpoint and onto JSON endpoints under /api/v5/payments. The company says the Classic Payment API “will continue to be fully supported,” so nothing is being switched off. Its developer documentation, however, now points new builds toward the JSON API, and stores that run custom PHP, headless checkouts, or gateway plugins built on transact.php are the ones with code to rewrite if they follow.
The playbook groups the work into four changes. A single endpoint driven by a type parameter becomes separate paths for each operation. Flat form fields become JSON objects. The API key moves out of the request body and into an HTTP header. Query-string replies become JSON bodies paired with standard HTTP status codes.
What stays the same
NMI says merchants do not need new API keys or permission changes, because existing keys and their permissions carry over to the new endpoints. Browser-side tokenization is also untouched. Stores that already use Collect.js or NMI’s newer Payment Component keep that front-end code. Only the server request changes, with the token moving from a payment_token form field to payment_details.payment_token in the JSON body.
The Collect.js documentation now carries a note that it is “intended for existing Collect.js integrations” and that new integrations should use the Payment Component with the NMI REST API. Collect.js tokens remain single use and expire 24 hours after creation, according to the same page.
The header detail that breaks first
On transact.php, the private key travels as a security_key field in the form-encoded body. On the JSON endpoints it goes in the Authorization header, and the playbook’s FAQ is specific about the format. The header value must be the merchant private API key and nothing else. NMI says to omit “Bearer,” “ApiKey,” or any other scheme, and it lists a failing Bearer header as the first question developers ask after migrating. Teams that reuse an HTTP helper written for another payments API should confirm it does not add a Bearer prefix on its own. NMI’s authentication reference uses https://sandbox.nmi.com/api/v5 in its examples and lists a missing header or an incorrect key format among the causes of a 401 Unauthorized reply.
How the requests change shape
NMI’s side-by-side code examples show the mapping for common calls:
- A sale that posted
type=salewithccnumber,ccexp, andcvvbecomesPOST /api/v5/payments/salewith those values inside apayment_detailsobject ascard_number,card_exp, andcard_cvv. - Name and address fields move into a
billing_addressobject, with a matchingshipping_addressobject available. - A capture moves the transaction ID out of the body and into the path, as
POST /api/v5/payments/{transaction_id}/capture, with the amount sent as a two-decimal string. A lower amount performs a partial capture. - Saving a card to the Customer Vault changes from
customer_vault=add_customerto acustomer_vaultobject withadd_to_vaultset to true and an optional custom ID. NMI assigns an ID if none is supplied. - A single transaction lookup becomes
GET /api/v5/payments/{transaction_id}.
Follow-on calls use the id field from the JSON response. A successful sale in NMI’s example returns HTTP 200 with "object": "transaction", the familiar response value of 1 and response_code of 100, plus a masked card number, card BIN, AVS and CVV results, and an actions array that records each step, such as an auth followed by a capture.
Error handling is the bigger rewrite
Classic integrations parse every reply the same way. In NMI’s example, a sale missing its amount on transact.php still comes back as HTTP 200 with a plain-text body of response=3 and response_code=300. The same mistake on the JSON endpoint returns HTTP 400 with a body that names the problem: type set to validationError, error_code set to E_INVALID_SUBMISSION, and a details array that points to the amount field. The playbook tells developers to check the HTTP status first and then read type, error_code, and message, citing codes such as 404 and 409 as examples.
That matters for PHP stacks in particular. Guzzle, a widely used PHP HTTP client, throws an exception on 4xx responses unless its http_errors option is turned off, so a gateway class that once read every reply as a string can start failing in a different code path. The examples reviewed for this article do not show the JSON reply for a bank decline. The playbook says the response, response_text, and response_code fields are maintained, but whether a decline arrives with HTTP 200 or an error status is something to confirm in the sandbox rather than assume.
Webhooks and in-person payments
The playbook does not list changes to webhook delivery. NMI’s webhook reference describes JSON event payloads signed with HMAC-SHA256 in a Signature header, with transaction events such as transaction.sale.success. Teams that reconcile orders from those events will still want to confirm that their order records store the transaction id the new endpoints return.
Merchants with card-present devices get a separate path. Device sales move to asynchronous endpoints such as POST /api/v5/devices/{device_id}/payment-requests/sale, and the integration polls a status URL until the payment request finishes.
Why high-risk stacks should plan now
Analysis. NMI does not set a deadline, which is the reason to schedule the work on the merchant’s own terms. High-risk stores often run NMI in front of a specialty acquirer, and a broken gateway deploy on a monitored account can turn into failed orders and support tickets before anyone notices the cause. EC4IM’s comparison of NMI and Authorize.Net for high-risk e-commerce covers why so many of these merchants sit on NMI in the first place.
A practical migration starts with an inventory of every place the code posts to transact.php, including refunds, voids, validates, and vault updates that are easy to miss because they run outside checkout. Each operation can then move behind a switch and be tested in the sandbox, one at a time. NMI says validate keeps the same permission rules on /api/v5/payments/validate that it had on the classic endpoint. Stores that rely on a WooCommerce gateway plugin rather than custom code should ask the plugin vendor which endpoint the plugin calls and whether a JSON release is planned, a question that fits alongside the checks in EC4IM’s WooCommerce payment plugin checklist for Authorize.Net and NMI stores.
The migration in brief
NMI’s playbook moves server-side payments from one form-encoded endpoint to per-operation JSON paths, with the private key alone in the Authorization header, card and address data in nested objects, and errors reported through HTTP status codes. Keys, permissions, and Collect.js or Payment Component tokens carry over, and the classic API stays supported. The work that remains for each store is finding every transact.php call, reshaping requests and error handling, and testing declines before the switch reaches live traffic.