ROKIConnect
Connectors Start API SDKs Migrate MCP Portal ES

Verified traps

12.1 Unknown fields are ignored - but the response now tells you

A body with a misspelled field name still returns 201 Created, and the payment is still created without that feature. What changed on 2026-08-17 is that the response carries a warnings array naming every field that was dropped:

{ "amount": 100, "service_fee": true }     <- wrong name
-> 201 Created
   "service_fee_amount": 0                  <- the fee pass-through was NOT applied
   "warnings": ["Campo desconocido ignorado: service_fee"]

Read that array. It is the cheapest correctness check this API offers.

Behaviour of the field, verified:

Situation warnings
Clean payload absent from the response - not null, not []
One unknown field array with one string
Several array with one string each, one per field
Accept-Language: en translated: "Unknown field ignored: save_card"

Because the text is localised, branch on the array being non-empty, never on its wording.

This used to be the most dangerous failure mode of the API, because the result looked successful, and it is still dangerous for code that ignores the new field - especially AI-generated code, which writes other gateways' names (card_token, payment_method, customer_id, amount_cents) with full confidence.

Defence, in order of strength:

  1. Validate before sending against the schema in openapi.yaml, which declares additionalProperties: false precisely to catch this. A request that never leaves your process cannot create a wrong payment.
  2. Log warnings on every response, and fail loudly in development. An empty deployment log is the only proof your field names are right.
  3. Verify the computed fields (sales_tax_amount, service_fee_amount, total, reusable) against what you intended. Warnings catch a name you invented; they do not catch a value the API computed differently from what you assumed.

12.2 amount has no ceiling and coerces types

The API accepts absurd amounts (11 digits) and numeric strings ("150.00"). Enforce a sane maximum before sending.

12.3 Three different kinds of 404 - tell them apart by code, not by the message

Every error response carries a stable code next to the localized message. On a 404 it is the only reliable way to know what actually went wrong:

code Meaning Fix
payment_not_found The route exists and the identifier is well formed; no such record for this key. Usually an id from the other environment. Use the key from the same environment as the id.
transaction_id_expected The route exists and rejected the identifier: you passed the numeric payment id (or anything that is not a UUID) where the transaction UUID belongs. Send transaction_id, not id (13.1).
not_found The path is not in the router at all. Malformed URL, missing /v1, or a removed endpoint. Fix the URL. No change of identifier will help.

Telling them apart saves hours, and each points at a different fix.

Do not classify these by message text. Two reasons, both verified:

  1. Until 2026-08-21 the numeric-id mistake returned the routing 404, so the whole ecosystem learned to test for "could not be found". It no longer returns that. Code carrying that test now files the single most common identifier mistake under "no such payment" and sends the developer hunting for a bad id instead of a bad identifier type.
  2. payment_not_found and transaction_id_expected messages are localized by Accept-Language; only the not_found prose stays English. A text match that works in one language breaks in the other.

12.4 Timestamps are Honduras local time with no offset

expires_at, created_at and paid_at come back as YYYY-MM-DD HH:MM:SS in Honduras time (UTC-6), with no timezone marker in the string. Parsing them as UTC shifts every value by six hours - enough to make a future expiry look expired, or an order look paid before it was created. Attach the offset explicitly when parsing.

12.5 A customer-selected tip is not in the total you were given

With tip_customer_selectable, the total returned at creation excludes the tip because the customer has not chosen it yet. The amount actually charged can therefore be higher than the total you stored. Reconcile against the webhook payload or a fresh lookup, never against the creation response.

12.6 metadata can come back as an empty array

When no metadata was sent, the API may return "metadata": [] instead of {}. Strict deserializers that expect an object will throw. Treat both shapes as "no metadata".

12.7 Do not hardcode the checkout domain from the official docs

The checkout_url returned is on aura.roki.systems/pay/link/{slug}. Never hardcode or allow-list a checkout domain - always redirect to the exact checkout_url the API returned.

12.8 success_url accepts http://

Although the official documentation requires HTTPS, the API accepts unencrypted URLs. Use HTTPS anyway - it is a return URL in a payment flow.