# ROKI Connect > The payments API of ROKI (Honduras). This file, and the markdown it points to, are the > verified contract: every statement here was tested against the live API (last full verification: 2026-10-28). > Where the official documentation and this corpus disagree, this corpus is what the API does. For an AI coding agent, the better path is the Model Context Protocol server, which answers questions instead of making you read: connect `https://mcp.roki.la/mcp` over Streamable HTTP. It also validates a payload before you send it, which matters here because this API accepts unknown fields with a 201 and ignores them in silence. ## The full verified guide - [ROKI Connect - complete integration guide](https://mcp.roki.la/ROKI_CONNECT.md): every operation, the three integration modes, webhooks with HMAC verification, the error catalogue, and the traps that a 201 response hides. - [OpenAPI 3.1 specification](https://mcp.roki.la/openapi.yaml): 42 operations, 35 schemas. - [Corpus verification status](https://mcp.roki.la/status.json): the result of the daily contract test that runs this corpus against the live API. ## Single-file clients, generated from that specification No dependencies, one file each. They refuse unknown fields before the request leaves your process, which matters because the API would accept them with a 201 and ignore them. - [PHP](https://mcp.roki.la/sdk/RokiConnect.php) - [TypeScript / JavaScript](https://mcp.roki.la/sdk/roki-connect.ts) - [Python](https://mcp.roki.la/sdk/roki_connect.py) - [C# / .NET](https://mcp.roki.la/sdk/RokiConnect.cs) ## Operations - `GET /me` - Which merchant does this key belong to - `GET /payments` - List payments - `POST /payments` - Create a payment - `GET /payments/{id}` - Retrieve a payment - `POST /payments/{transaction_id}/void` - Void a transaction - `POST /payments/{transaction_id}/refund` - Refund a transaction - `GET /payments/{transaction_id}/receipt` - Get receipt metadata - `GET /payments/{transaction_id}/receipt/download` - Download the receipt PDF - `GET /payment-methods` - List a customer's saved cards - `DELETE /payment-methods/{payment_method_id}` - Revoke a saved card - `POST /payment-methods/{payment_method_id}/charge` - Charge a saved card - `POST /payments/token-charge` - Charge a saved card (alias) - `POST /embed/confirm` - Confirm an embedded-components payment - `POST /embed/checkouts/{checkout_id}/confirm` - Confirm an embedded checkout created by the server (obligations) - `GET /webhooks` - Read the webhook endpoint of this key's environment - `POST /webhooks` - Create the webhook endpoint, or change its URL - `POST /webhooks/rotate-secret` - Issue a new signing secret - `POST /webhooks/disable` - Pause delivery - `POST /webhooks/enable` - Resume delivery - `POST /webhooks/test` - Send a signed webhook.test ping - `GET /webhooks/deliveries` - List recent delivery attempts - `GET /obligations` - Look up a customer's open obligations (Collections, Education) - `GET /obligations/{obligation_id}` - Read one obligation - `POST /obligations/{obligation_id}/pay` - Create a payment locked to an obligation's amount - `GET /automatic-debit/agreement` - Read the merchant's automatic-debit agreement - `POST /automatic-debit/authorizations` - Enrol a card for automatic debit (1.00 HNL + 3DS + void) - `GET /automatic-debit/authorizations/{authorization_id}` - Read an enrolment - `POST /payment-authorizations` - Create a hold (authorize now, capture or release later) - `GET /payment-authorizations/{payment_authorization_id}` - Read a hold - `POST /payment-authorizations/{payment_authorization_id}/capture` - Capture the full authorized amount - `POST /payment-authorizations/{payment_authorization_id}/release` - Release (void) an authorized hold - `GET /recurring` - List recurring arrangements - `POST /recurring` - Create a recurring arrangement on a saved card - `GET /recurring/{recurring_id}` - Read a recurring arrangement - `DELETE /recurring/{recurring_id}` - Cancel a recurring arrangement - `POST /recurring/{recurring_id}/charge` - Trigger a merchant-initiated charge (no 3DS) - `POST /sales/sessions` - Create a locked sale session (mode 4, hosted fields) - `GET /sales/sessions/{session_id}` - Read a sale session's state - `POST /oauth/account-lookup` - Does this email already have a ROKI merchant account (partners only) - `POST /oauth/token` - Exchange an authorization code, or refresh a token (Continue with Roki) - `GET /oauth/authorize` - Start Continue with Roki (browser redirect, Authorization Code + PKCE) - `POST (webhook, ROKI calls you) (your registered endpoint)` - Payment event sent by ROKI ## Sections - [1. 1. What ROKI Connect is](https://mcp.roki.la/ROKI_CONNECT.md#1-what-roki-connect-is): It lets a merchant's server accept card payments in **five modes**. In every one, the merchant - [2. 2. Architecture and flow](https://mcp.roki.la/ROKI_CONNECT.md#2-architecture-and-flow): ``` - [3. 3. Base URL and versioning](https://mcp.roki.la/ROKI_CONNECT.md#3-base-url-and-versioning): ``` - [4. 4. Authentication](https://mcp.roki.la/ROKI_CONNECT.md#4-authentication): One header, on every request: - [5. 5. Credentials: obtaining and storing](https://mcp.roki.la/ROKI_CONNECT.md#5-credentials-obtaining-and-storing): > **The merchant portal is in Spanish.** Every portal label in this guide - **Sandbox (prueba) / - [6. 6. Response language](https://mcp.roki.la/ROKI_CONNECT.md#6-response-language): Optional header `Accept-Language: es` or `en`. Spanish is the default. - [7. 7. Creating a payment](https://mcp.roki.la/ROKI_CONNECT.md#7-creating-a-payment): ```http - [8. 8. Calculations: tax, tip and fee pass-through](https://mcp.roki.la/ROKI_CONNECT.md#8-calculations-tax-tip-and-fee-pass-through): - [9. 9. Retrieving a payment, and its lifecycle](https://mcp.roki.la/ROKI_CONNECT.md#9-retrieving-a-payment-and-its-lifecycle): ```http - [10. 10. Customer data: `metadata`](https://mcp.roki.la/ROKI_CONNECT.md#10-customer-data-metadata): The customer's details are entered on ROKI's page. You can **prefill** the ones you already know - [11. 11. Idempotency and duplicates](https://mcp.roki.la/ROKI_CONNECT.md#11-idempotency-and-duplicates): Optional but **always recommended** on creation: - [12. 12. Verified traps](https://mcp.roki.la/ROKI_CONNECT.md#12-verified-traps): - [13. 13. Void, refund and receipts](https://mcp.roki.la/ROKI_CONNECT.md#13-void-refund-and-receipts): - [14. 14. Webhooks](https://mcp.roki.la/ROKI_CONNECT.md#14-webhooks): They are the authoritative confirmation of a charge. Without them, an integration depends on the - [15. 15. Error catalogue](https://mcp.roki.la/ROKI_CONNECT.md#15-error-catalogue): Every error carries a stable `code` alongside the human `message`. Successful responses carry no - [16. 16. Environments, testing and local development](https://mcp.roki.la/ROKI_CONNECT.md#16-environments-testing-and-local-development): - [17. 17. Quickstart](https://mcp.roki.la/ROKI_CONNECT.md#17-quickstart): ```bash - [18. 18. Mobile applications (iOS / Android)](https://mcp.roki.la/ROKI_CONNECT.md#18-mobile-applications-ios-android): There is no ROKI mobile SDK. A mobile app integrates through the same hosted-checkout flow as the - [19. 19. Integration examples by stack](https://mcp.roki.la/ROKI_CONNECT.md#19-integration-examples-by-stack): - [1. 1. Configuration: keys from a settings table, not from code](https://mcp.roki.la/ROKI_CONNECT.md#1-configuration-keys-from-a-settings-table-not-from-code): Both secrets live in a `settings` row, encrypted at rest. Rotating a key is an `UPDATE`, not a deploy. - [2. 2. The API client](https://mcp.roki.la/ROKI_CONNECT.md#2-the-api-client): Exceptions first, so the calling code can branch on the real failure modes. - [3. 3. Checkout: create on order confirmation, verify, then redirect](https://mcp.roki.la/ROKI_CONNECT.md#3-checkout-create-on-order-confirmation-verify-then-redirect): Local mirror of the payment. `GET /payments` can list them, but this table is what knows which ones you are still waiting on, without depending on a n - [4. 4. Webhook handler](https://mcp.roki.la/ROKI_CONNECT.md#4-webhook-handler): Register the endpoint in the portal at `/merchant/connect/webhooks`. URLs and signing secrets are **separate per environment**; a sandbox secret will - [5. 5. Polling fallback](https://mcp.roki.la/ROKI_CONNECT.md#5-polling-fallback): Webhooks fail: a deploy, a DNS blip, a rotated secret. Two sweeps, in opposite directions. The first drives from your own `roki_payments` rows and ask - [1. 1. Configuration from a settings store](https://mcp.roki.la/ROKI_CONNECT.md#1-configuration-from-a-settings-store): The key prefix alone selects the environment (`sk_test_` sandbox, `sk_live_` production) over identical routes, so the environment is derived, never c - [2. 2. API client](https://mcp.roki.la/ROKI_CONNECT.md#2-api-client): `createPayment()` and `getPayment()`, with a per-attempt `AbortController` timeout, a content-derived `Idempotency-Key`, retries that reuse that key, - [3. 3. Checkout flow](https://mcp.roki.la/ROKI_CONNECT.md#3-checkout-flow): Create the payment when the order is confirmed, claim the idempotency key in the database *before* the network call so a double-submitted form cannot - [4. 4. Webhook handler](https://mcp.roki.la/ROKI_CONNECT.md#4-webhook-handler): **The signature is computed over the raw request bytes.** `express.json()` consumes the stream and leaves you only a parsed object; re-serializing it - [5. 5. Polling fallback](https://mcp.roki.la/ROKI_CONNECT.md#5-polling-fallback): Webhooks fail: your endpoint is down during a deploy, the delivery is dropped, DNS blips. `GET /payments/{id}` is the other source of truth, and `GET - [1. 1. Configuration: credentials from a settings table](https://mcp.roki.la/ROKI_CONNECT.md#1-configuration-credentials-from-a-settings-table): The secret key and the webhook signing secret live in a `settings` table, not in the image or the - [2. 2. The API client](https://mcp.roki.la/ROKI_CONNECT.md#2-the-api-client): This client covers only what mode 1 checkout needs: `createPayment` (`POST /payments`) and - [3. 3. Checkout flow](https://mcp.roki.la/ROKI_CONNECT.md#3-checkout-flow): Create the payment when the order is confirmed, persist before the network call and again after it, - [4. 4. Webhook handler](https://mcp.roki.la/ROKI_CONNECT.md#4-webhook-handler): The HMAC covers the exact bytes ROKI sent. Read the raw body with `await request.body()` **before** - [5. 5. Polling fallback](https://mcp.roki.la/ROKI_CONNECT.md#5-polling-fallback): Webhooks get lost: a deploy restarts the process mid-request, a proxy times out, DNS blips, or a - [1. 1. Configuration: keys in a settings table, never in code](https://mcp.roki.la/ROKI_CONNECT.md#1-configuration-keys-in-a-settings-table-never-in-code): Hardcoding `sk_live_...` in a PHP file means a key rotation needs a redeploy, and the key ends up in - [2. 2. The API client](https://mcp.roki.la/ROKI_CONNECT.md#2-the-api-client): Three things this client gets right and most hand-written ones do not: - [3. 3. Checkout flow](https://mcp.roki.la/ROKI_CONNECT.md#3-checkout-flow): Create the payment **server-side, when the order is confirmed**, persist the identifiers before you - [4. 4. Webhook handler](https://mcp.roki.la/ROKI_CONNECT.md#4-webhook-handler): Rules: verify the HMAC over the **raw** body before parsing anything, compare in constant time, return - [5. 5. Polling fallback](https://mcp.roki.la/ROKI_CONNECT.md#5-polling-fallback): Webhooks get lost: a DNS blip, an expired certificate, a WAF rule, a maintenance window. Poll the - [20. 20. Reconciliation and operations runbook](https://mcp.roki.la/ROKI_CONNECT.md#20-reconciliation-and-operations-runbook): Void, refund, receipts and listing all exist now, so recovery is possible - but only if you decided - [21. 21. Pre-production checklist](https://mcp.roki.la/ROKI_CONNECT.md#21-pre-production-checklist): - [ ] Credentials are read at runtime from configuration or the database, not from code. - [22. 22. Mode 2 - Embedded components](https://mcp.roki.la/ROKI_CONNECT.md#22-mode-2-embedded-components): Card fields rendered by ROKI inside a **secure iframe on your own site**. The customer never leaves - [23. 23. Mode 3A - Saved cards (tokenized payments)](https://mcp.roki.la/ROKI_CONNECT.md#23-mode-3a-saved-cards-tokenized-payments): Charge a card the customer already saved, with no new card entry. This is what makes subscriptions - [24. 24. Troubleshooting](https://mcp.roki.la/ROKI_CONNECT.md#24-troubleshooting): | Symptom | Likely cause | What to do | - [25. 25. Keeping this document honest](https://mcp.roki.la/ROKI_CONNECT.md#25-keeping-this-document-honest): This API has already shipped two silent breaking changes: the move to `/v1` removed the unversioned - [26. 26. Mode 3 - Recurring payments](https://mcp.roki.la/ROKI_CONNECT.md#26-mode-3-recurring-payments): A recurring arrangement (`rec_*`) anchors a schedule - amount, frequency, billing day - to a saved - [27. 27. Mode 4 - Hosted fields](https://mcp.roki.la/ROKI_CONNECT.md#27-mode-4-hosted-fields): You build the whole checkout page. ROKI hosts three iframes - card number, expiry, CVV - that you - [28. 28. Managing the webhook endpoint by API](https://mcp.roki.la/ROKI_CONNECT.md#28-managing-the-webhook-endpoint-by-api): Everything the portal's Webhooks tab does, with the secret key. One endpoint per environment: - [29. 29. Payment authorizations (holds)](https://mcp.roki.la/ROKI_CONNECT.md#29-payment-authorizations-holds): Authorize now, capture or release later - a generic hold, not a sale and not automatic debit. You - [30. 30. Obligations (Collections and Education)](https://mcp.roki.la/ROKI_CONNECT.md#30-obligations-collections-and-education): Find what a customer owes in the merchant's Collections invoices and Education installments, then - [31. 31. Automatic debit - enrolling a card](https://mcp.roki.la/ROKI_CONNECT.md#31-automatic-debit-enrolling-a-card): Same flow as the merchant portal's automatic debit: ROKI charges 1.00 HNL with 3-D Secure, voids - [32. 32. Continue with Roki (OAuth 2 + PKCE)](https://mcp.roki.la/ROKI_CONNECT.md#32-continue-with-roki-oauth-2-pkce): For partner platforms: connect a merchant without the merchant pasting a secret key into your ## Things an agent gets wrong here - `service_fee` is not a field. The field is `service_fee_enabled`, a boolean, and the amounts come back recalculated in reverse. Sending `service_fee` returns 201 and changes nothing. - Void, refund and receipts are keyed by the `transaction_id` UUID, not by the numeric payment `id`. Using the numeric id returns a routing 404 that looks like "the endpoint does not exist". - A redirect back from the checkout is not proof of payment. Confirm with the webhook or by polling `GET /payments/{id}`. - The webhook signature is HMAC-SHA256 over `timestamp + "." + raw_body`. Re-serializing the parsed JSON produces a different string and the signature will never match.