# Quickstart (/docs/getting-started) This quickstart walks through the three API calls every integration makes: exchange your client secret for a JWT, create a payment, and handle the webhook that tells you whether the customer paid. ## Prerequisites [#prerequisites] * A verified merchant account ([sign up](/docs/saas-dashboard/getting-started)) * At least one bank destination configured in the dashboard * An API key with a webhook URL — create one at **Open Finance → API Keys** Your client secret is shown **once** at creation time. Store it in an environment variable or a secrets manager on your backend. Never put it in client-side code, browser storage, or a public repository. If it leaks, revoke the key immediately. ## 1. Get an access token [#1-get-an-access-token] Exchange your client secret for a short-lived JWT. Tokens are valid for 60 minutes. ```bash curl -X POST https://payment-api.openfinance.aryze.io/v1/auth/token \ -H 'Content-Type: application/json' \ -d '{ "grant_type": "client_credentials", "client_secret": "'"$ARYZE_CLIENT_SECRET"'" }' ``` ```ts const res = await fetch( 'https://payment-api.openfinance.aryze.io/v1/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_secret: process.env.ARYZE_CLIENT_SECRET, }), }, ); if (!res.ok) throw new Error(`Auth failed: ${res.status}`); const { access_token } = await res.json(); ``` ```python import os import requests res = requests.post( 'https://payment-api.openfinance.aryze.io/v1/auth/token', json={ 'grant_type': 'client_credentials', 'client_secret': os.environ['ARYZE_CLIENT_SECRET'], }, ) res.raise_for_status() access_token = res.json()['access_token'] ``` ```csharp using System.Net.Http.Json; var http = new HttpClient(); var res = await http.PostAsJsonAsync( "https://payment-api.openfinance.aryze.io/v1/auth/token", new { grant_type = "client_credentials", client_secret = Environment.GetEnvironmentVariable("ARYZE_CLIENT_SECRET"), }); res.EnsureSuccessStatusCode(); var body = await res.Content.ReadFromJsonAsync(); var accessToken = body!.access_token; record TokenResponse(string access_token, string token_type, int expires_in, string environment); ``` The response contains the token you'll pass as `Authorization: Bearer ` on every subsequent call. ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "environment": "sandbox" } ``` ## 2. Initiate a payment [#2-initiate-a-payment] Create a payment session. The destination bank account is read from the API key you authenticated with, so you don't pass it in the request. ```bash curl -X POST https://payment-api.openfinance.aryze.io/v1/payments/initiate \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -d '{ "redirectUrl": "https://yourapp.com/checkout/return", "amount": 100.50, "currencyCode": "EUR", "reference": "ORDER12345" }' ``` ```ts const res = await fetch( 'https://payment-api.openfinance.aryze.io/v1/payments/initiate', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${access_token}`, }, body: JSON.stringify({ redirectUrl: 'https://yourapp.com/checkout/return', amount: 100.5, currencyCode: 'EUR', reference: 'ORDER12345', }), }, ); const payment = await res.json(); // Redirect the customer to complete the payment: // window.location.href = payment.flowUrl; ``` ```python res = requests.post( 'https://payment-api.openfinance.aryze.io/v1/payments/initiate', headers={'Authorization': f'Bearer {access_token}'}, json={ 'redirectUrl': 'https://yourapp.com/checkout/return', 'amount': 100.50, 'currencyCode': 'EUR', 'reference': 'ORDER12345', }, ) res.raise_for_status() payment = res.json() # Redirect the customer to payment['flowUrl'] ``` ```csharp http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var res = await http.PostAsJsonAsync( "https://payment-api.openfinance.aryze.io/v1/payments/initiate", new { redirectUrl = "https://yourapp.com/checkout/return", amount = 100.50m, currencyCode = "EUR", reference = "ORDER12345", }); res.EnsureSuccessStatusCode(); var payment = await res.Content.ReadFromJsonAsync(); ``` The response gives you a `flowUrl` — redirect the customer there. They'll choose their bank, authenticate, and authorise the transfer. ```json { "transactionId": "2f1a3e8c-9b7d-4a11-8c6f-d3e5e9b1a2c0", "mastercardPaymentId": "mcob_8f3c2a...", "flowUrl": "https://payment.aryze.io/complete/abc123...", "status": "PENDING", "amount": 100.50, "currencyCode": "EUR", "reference": "ORDER12345", "endToEndId": "E2E-ORDER12345", "initiationTimestamp": "2026-04-21T14:30:00Z" } ``` ## 3. Handle the webhook [#3-handle-the-webhook] When the payment status changes, ARYZE POSTs a `payment.status_updated` event to the webhook URL on your API key. Always respond with `200 OK`, then do any slow work asynchronously. ```ts title="Express.js" import express from 'express'; const app = express(); app.use(express.json()); app.post('/webhooks/aryze', async (req, res) => { const { event, transactionId, status, reference } = req.body; // Acknowledge immediately, then process. res.status(200).json({ received: true }); if (event === 'payment.status_updated' && status === 'COMPLETED') { await markOrderPaid(reference, transactionId); } }); app.listen(3000); ``` Webhook retries use fixed backoff: 3 attempts at 1s, 5s, and 15s. Make your handler **idempotent** — the same event may arrive more than once. ## Next steps [#next-steps] # ARYZE Open Finance (/docs) ARYZE Open Finance lets you accept bank-to-bank payments from your customers via Mastercard Open Banking. You get a single REST API, real-time webhooks for payment status, and a dashboard to manage merchants, bank destinations, API keys, and transactions. Pick a starting point: ## Base URL [#base-url] All Payment API endpoints are served from a single base URL and versioned under `/v1`: ## Before you integrate [#before-you-integrate] 1. Sign up and verify your organization in the [Open Finance Payments Dashboard](/docs/saas-dashboard/getting-started). 2. Submit KYB so your account can issue live API keys. 3. Add at least one bank destination to receive funds. 4. Create an API key with a webhook URL — both [Payment API auth](/docs/api-reference/payment-api/create-access-token) and webhook delivery rely on it. # API reference (/docs/api-reference) These pages are generated from the [OpenAPI spec](https://github.com/aryze-fintech/aryze_open_finance_docs/blob/main/content/openapi/payment-api.json) committed to this repo. Every endpoint renders a live request playground — paste a Bearer token, fill in the body, and fire the call straight at the production API. ## Base URL [#base-url] All endpoints are versioned under `/v1`. ## Authentication [#authentication] Every request except `POST /v1/auth/token` requires a Bearer JWT in the `Authorization` header. See [Create access token](/docs/api-reference/payment-api/create-access-token). # Accept a payment (/docs/guides/accept-a-payment) This guide shows the full happy path for a single payment: your server calls `/v1/payments/initiate`, the customer completes the bank transfer through the ARYZE-hosted flow, and your return URL receives them when they're done. The customer's bank does the actual authentication and authorisation. Your server never sees bank credentials — you only receive a transaction status. ## 1. Set up credentials on your backend [#1-set-up-credentials-on-your-backend] All Payment API calls must come from your server. The client secret never reaches the browser. ```bash title=".env" ARYZE_CLIENT_SECRET=cs_live_... ARYZE_API_BASE=https://payment-api.openfinance.aryze.io ``` ## 2. Create a payment when the customer checks out [#2-create-a-payment-when-the-customer-checks-out] ### Get an access token [#get-an-access-token] Tokens are valid for 60 minutes. Cache the token on your backend and reuse it until it's close to expiry — don't request a fresh one on every payment. ```ts async function getAccessToken(): Promise { const res = await fetch(`${process.env.ARYZE_API_BASE}/v1/auth/token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_secret: process.env.ARYZE_CLIENT_SECRET, }), }); if (!res.ok) throw new Error(`Auth failed: ${res.status}`); const { access_token } = await res.json(); return access_token; } ``` ### Initiate the payment [#initiate-the-payment] Pass a unique `reference` (your internal order ID) and the URL you want the customer returned to. Amounts are decimal numbers in the payment currency — `100.50` means 100 euros and 50 cents, not 10050 minor units. ```ts async function createPayment(orderId: string, amount: number) { const token = await getAccessToken(); const res = await fetch( `${process.env.ARYZE_API_BASE}/v1/payments/initiate`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ redirectUrl: `https://yourapp.com/checkout/return?order=${orderId}`, amount, currencyCode: 'EUR', reference: orderId, metadata: JSON.stringify({ orderId, source: 'web-checkout' }), }), }, ); if (!res.ok) { const { error } = await res.json().catch(() => ({ error: res.statusText })); throw new Error(`Payment initiation failed: ${error}`); } return res.json(); } ``` ### Redirect to the flow URL [#redirect-to-the-flow-url] The response contains a `flowUrl` — the hosted page where the customer picks their bank and approves the transfer. Expose your own checkout route on your backend, return `flowUrl` to your frontend, and redirect. `/api/checkout` here is *your* server route, not an ARYZE endpoint. ```ts title="Your backend (e.g. Express)" app.post('/api/checkout', async (req, res) => { const payment = await createPayment(req.body.orderId, req.body.amount); res.json({ flowUrl: payment.flowUrl, transactionId: payment.transactionId }); }); ``` ```ts title="Your frontend" const { flowUrl } = await fetch('/api/checkout', { method: 'POST', body: JSON.stringify({ orderId, amount }), headers: { 'Content-Type': 'application/json' }, }).then((r) => r.json()); window.location.href = flowUrl; ``` ## 3. Handle the return URL [#3-handle-the-return-url] After the customer finishes in their banking app, they're redirected to the `redirectUrl` you provided. **Do not trust the return URL alone** to mark the order as paid — the authoritative signal is the webhook. Use the return URL only to render a "thanks" page and poll for status. ```ts title="app/checkout/return/page.tsx" export default async function ReturnPage({ searchParams, }: { searchParams: { order?: string }; }) { const order = searchParams.order; const status = await getOrderStatus(order); // your DB, updated by webhook if (status === 'paid') return ; if (status === 'failed') return ; return ; // polls every few seconds } ``` ## 4. Finalise the order from the webhook [#4-finalise-the-order-from-the-webhook] The webhook handler is where you actually mark the order as paid. See [Consume webhooks](/docs/guides/consume-webhooks) for retry semantics and idempotency. ## Field rules to know [#field-rules-to-know] Pick a `reference` that is unique per order **after sanitisation** — if two different order IDs collapse to the same 20-character alphanumeric string, you won't be able to tell them apart in your bank statement. # Check payment status (/docs/guides/check-payment-status) Webhooks are the primary way to learn about status changes. Polling `GET /v1/payments/{transactionId}` is useful for: * A safety net in case a webhook is delayed or lost. * Back-office tools that inspect a single transaction on demand. * Reconciliation jobs that verify your local state against ARYZE. Don't poll in a tight loop. Bank settlement can take seconds to minutes — use at least a 5-second interval, stop as soon as the status is terminal (`COMPLETED` or `FAILED`), and cap the total duration (for example, 10 minutes). ## Fetch the transaction [#fetch-the-transaction] ```bash curl https://payment-api.openfinance.aryze.io/v1/payments/2f1a3e8c-9b7d-4a11-8c6f-d3e5e9b1a2c0 \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```ts async function getPayment(transactionId: string, token: string) { const res = await fetch( `${process.env.ARYZE_API_BASE}/v1/payments/${transactionId}`, { headers: { Authorization: `Bearer ${token}` } }, ); if (res.status === 404) return null; if (!res.ok) throw new Error(`Lookup failed: ${res.status}`); return res.json(); } ``` ```python def get_payment(transaction_id: str, token: str): res = requests.get( f'https://payment-api.openfinance.aryze.io/v1/payments/{transaction_id}', headers={'Authorization': f'Bearer {token}'}, ) if res.status_code == 404: return None res.raise_for_status() return res.json() ``` ```csharp public async Task GetPaymentAsync(string transactionId, string token) { http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var res = await http.GetAsync( $"https://payment-api.openfinance.aryze.io/v1/payments/{transactionId}"); if (res.StatusCode == HttpStatusCode.NotFound) return null; res.EnsureSuccessStatusCode(); return await res.Content.ReadFromJsonAsync(); } ``` ## Status values [#status-values] You may have seen `CANCELLED` in the dashboard's status filter — that's a UI option, but no transaction ever sits in `CANCELLED` at the API level. A cancelled bank transfer surfaces as `status: "FAILED"` with `mastercardStatus: "CANCELLED"`. ## Polling pattern [#polling-pattern] ```ts async function waitForTerminalStatus( transactionId: string, token: string, { intervalMs = 5000, timeoutMs = 10 * 60 * 1000, }: { intervalMs?: number; timeoutMs?: number } = {}, ) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const payment = await getPayment(transactionId, token); if (!payment) return { status: 'NOT_FOUND' } as const; if (payment.status === 'COMPLETED' || payment.status === 'FAILED') { return payment; } await new Promise((r) => setTimeout(r, intervalMs)); } return { status: 'TIMEOUT' } as const; } ``` ## Response [#response] The transaction response includes both the ARYZE status and the underlying Mastercard status, plus any metadata you stored at initiation. ```json { "transactionId": "2f1a3e8c-9b7d-4a11-8c6f-d3e5e9b1a2c0", "mastercardPaymentId": "mcob_8f3c2a...", "amount": 100.50, "currencyCode": "EUR", "status": "COMPLETED", "mastercardStatus": "PAYMENT_EXECUTED_CREDITED", "paymentRail": "SepaCreditTransfer", "reference": "ORDER12345", "endToEndId": "E2E-ORDER12345", "redirectUrl": "https://yourapp.com/checkout/return", "flowUrl": "https://payment.aryze.io/complete/abc123...", "providerId": "bank_xyz", "metadata": "{\"orderId\":\"ORDER12345\"}", "initiationTimestamp": "2026-04-21T14:30:00Z", "updatedTimestamp": "2026-04-21T14:32:10Z", "completedTimestamp": "2026-04-21T14:32:10Z" } ``` See the [full field reference](/docs/api-reference/payment-api/get-payment). # Consume webhooks (/docs/guides/consume-webhooks) Whenever a payment's status changes, ARYZE POSTs a JSON body to the webhook URL configured on your API key. There is one event type today: `payment.status_updated`. ## Configure your webhook URL [#configure-your-webhook-url] Webhook URLs are set per API key in the dashboard at **Open Finance → API Keys**. You can update the URL without rotating the key. The endpoint must be HTTPS and reachable from the public internet — non-HTTPS URLs are rejected on save. ## Event shape [#event-shape] ```json { "event": "payment.status_updated", "transactionId": "2f1a3e8c-9b7d-4a11-8c6f-d3e5e9b1a2c0", "mastercardPaymentId": "mcob_8f3c2a...", "status": "COMPLETED", "amount": 100.50, "currencyCode": "EUR", "timestamp": "2026-04-21T14:32:10Z" } ``` Your merchant reference and any metadata you sent on `POST /v1/payments/initiate` are **not** included in the webhook. Look them up by `transactionId` against your own store — you should have saved them when you created the payment. ## Acknowledge quickly, process later [#acknowledge-quickly-process-later] Return `200 OK` as soon as you've persisted the event. Do the real work (fulfilment, emails, accounting) in a background job. ARYZE treats any non-2xx response as failure and retries. ```ts title="Express.js" import express from 'express'; const app = express(); app.use(express.json()); app.post('/webhooks/aryze', async (req, res) => { const event = req.body; try { await saveEventIfNew(event); // idempotency check res.status(200).json({ received: true }); } catch (err) { console.error('Webhook persistence failed', err); res.status(500).json({ error: 'retry please' }); return; } // Off the request path — don't await this. enqueueFulfilmentJob(event).catch(console.error); }); app.listen(3000); ``` ## Idempotency [#idempotency] ARYZE may deliver the same event more than once (retries, network retransmits, or the same transaction moving through the same terminal state). Store each event by `transactionId` + `status` and skip work you've already done. ```ts async function saveEventIfNew(event: WebhookEvent) { const key = `${event.transactionId}:${event.status}`; const inserted = await db.webhookEvents.insert({ key, payload: event, receivedAt: new Date(), }).onConflict('key').ignore(); if (!inserted) throw new DuplicateEvent(key); } ``` ## Retry policy [#retry-policy] If your endpoint doesn't return a 2xx response within 10 seconds, ARYZE retries. There is **no HMAC signature** on webhook requests today. Until signing is available, use an unguessable webhook URL (for example, include a long random segment) and cross-check every event by fetching the transaction with `GET /v1/payments/{transactionId}` before acting on it. ## Handling each status [#handling-each-status] The bank has registered the payment but the customer has not yet completed authentication and authorisation. Show a "redirecting" or "waiting" UI. Do not fulfil the order. This is typically the first webhook you receive for a transaction. The bank is processing the transfer (preparing, authorising, or settling). Show a "processing" UI. Do not fulfil the order. You may receive `PENDING` more than once as the bank reports intermediate states upstream — ARYZE collapses several Mastercard codes (`PREPARING`, `AUTHORIZING`, `PROVIDER_PROCESSING`, `PENDING`) into the same transaction status. Funds have been credited. Fulfil the order. If you've already fulfilled this transaction from an earlier event, skip — the event can arrive more than once. The transfer was cancelled, rejected, or expired upstream. Do not fulfil the order. Mark it as failed and prompt the customer to retry if that makes sense for your flow. Mastercard `CANCELLED` and `REJECTED` codes both arrive here as `FAILED` — inspect the **Mastercard status** on the transaction (via [`GET /v1/payments/{transactionId}`](/docs/api-reference/payment-api/get-payment)) if you need the upstream reason. # Integration Guides (/docs/guides) Each guide solves one problem end-to-end with runnable code. # API keys (/docs/saas-dashboard/api-keys) An API key is the credential your backend uses to call the Payment API. Each key is bound to one environment (sandbox or live), one bank destination, and one webhook URL. ## Create a key [#create-a-key] ### Open the API keys page [#open-the-api-keys-page] Go to **Open Finance → API Keys**. Make sure the environment selector in the header matches where you want the key to run — sandbox keys can't be promoted to live later. ### Click "Create API key" [#click-create-api-key] Fill in: * **Name** — how the key shows up in the dashboard (e.g. "web-checkout prod"). * **Description** — optional notes for teammates. * **Destination** — which bank account will receive payments created with this key. * **Webhook URL** — where ARYZE POSTs `payment.status_updated` events. Must be HTTPS. * **Expiration** — either "never expires" or a specific date. ### Save the client secret [#save-the-client-secret] The client secret is shown **once** immediately after creation. Copy it into your secrets manager or environment variable store before closing the modal. If you miss the secret, you have to delete the key and create a new one. There is no way to recover a client secret later. ### Authenticate from your backend [#authenticate-from-your-backend] Send the client secret to [`POST /v1/auth/token`](/docs/api-reference/payment-api/create-access-token) to get a short-lived JWT, then use that JWT for payment calls. ## Activation and KYB [#activation-and-kyb] * **Sandbox keys** are active immediately. * **Live keys** are created as *inactive* until KYB is approved. Once approval lands, the key flips to active on its own — no action needed. ## Update a key [#update-a-key] For an existing key you can: * **Change the webhook URL** — takes effect on the next event. * **Change the description or expiration**. * **Rotate the webhook URL** without rotating the secret — useful for migrating webhook handlers. You **cannot** change the destination on a live key. Create a new key bound to the new destination, migrate your integration, then revoke the old one. ## Rotate or revoke a key [#rotate-or-revoke-a-key] Two separate operations: * **Rotate** — generate a new client secret for the same key. The old secret keeps working until you explicitly expire it, so you can roll it out without downtime. * **Revoke** — immediately disable the key. Any subsequent `POST /v1/auth/token` with its secret fails with `401 invalid_client`. In-flight payments complete normally. If a secret leaks, **revoke** first and create a new key with a new secret. Don't rely on rotation alone — a leaked secret can obtain tokens until it's explicitly revoked. ## Webhook URL requirements [#webhook-url-requirements] * HTTPS endpoint. * Reachable from the public internet. * Responds with any 2xx status within 10 seconds. * Unguessable path recommended (treat the URL as shared secret until HMAC signing ships). # Bank destinations (/docs/saas-dashboard/bank-destinations) A **destination** is a bank account that receives funds from customer payments. Each API key is bound to exactly one destination — which means the destination determines the currency and payment rail used for every payment created with that key. ## Add a destination [#add-a-destination] ### Open the bank destinations page [#open-the-bank-destinations-page] Go to **Open Finance → Banks**. You'll see a list of your existing destinations (empty if this is your first one) and a **Add destination** button. ### Pick a country [#pick-a-country] Select the country the bank account is held in. The payment rail and currency are inferred from your choice — for example, the United Kingdom implies `UkFasterPayments` + GBP, and Eurozone countries imply `SepaCreditTransfer` + EUR. The dashboard shows which rail/currency pair will apply before you confirm. The list of available countries depends on where your business is registered and your banking partner coverage. ### Enter account details [#enter-account-details] Provide the IBAN (or sort code + account number for UK accounts), the account holder name (must match your registered business name), and a human-readable label for internal use. ### Verify ownership [#verify-ownership] Depending on the rail, ARYZE may run a micro-deposit or account-name check before activating the destination. Follow the on-screen instructions. ## Sandbox vs live destinations [#sandbox-vs-live-destinations] Destinations, like API keys, are scoped to an environment: * **Sandbox destinations** don't move real money. They exist so you can exercise the full API surface during development. * **Live destinations** require an approved KYB submission on the organisation. Creating a live API key requires both an approved KYB submission **and** an active live destination in the matching currency. If either is missing, the "Create API key" button is disabled and the dashboard tells you what's left. # Getting started (/docs/saas-dashboard/getting-started) Complete these steps once per organisation. After that, everything else — destinations, more API keys, inviting teammates — is incremental.
Environment:
### Create an account [#create-an-account] Sign up with your work email. You'll be asked to verify your email address before you can sign in. A personal organisation is created for you automatically. ### Enable two-factor authentication [#enable-two-factor-authentication] 2FA is **required** before you can create live API keys. Add an authenticator app under **Account → Security**. ### Create a business organisation [#create-a-business-organisation] Under **Account → Organisations**, create an organisation for your company. All merchants, destinations, and API keys belong to an organisation, not to you personally — this is what lets you invite teammates later. ### Submit KYB [#submit-kyb] Open **Account → Verification** and start the KYB flow. You'll be walked through document upload and a beneficial-owner check. See [Verification](/docs/saas-dashboard/verification) for what to have ready. You can continue integrating in sandbox while KYB is reviewed. Live access unlocks once the assessment is approved. ### Register your merchant [#register-your-merchant] Go to **Account → Merchant** and fill in your company's details: legal name, registration number, country of incorporation, and contact information. This is the record your KYB assessment will be run against, so make sure the details match what's on your company registration documents. ### Add a bank destination [#add-a-bank-destination] Under **Open Finance → Banks**, connect the bank account where ARYZE should settle your payments. Each destination is tied to a currency and a payment rail (e.g. SEPA, Faster Payments). See [Bank destinations](/docs/saas-dashboard/bank-destinations). ### Create your first API key [#create-your-first-api-key] Under **Open Finance → API Keys**, click **Create API key**. Pick a destination, set a webhook URL, and copy the client secret — it's shown **once**. See [API keys](/docs/saas-dashboard/api-keys) for details. ### Send a sandbox payment [#send-a-sandbox-payment] Head to **Open Finance → Playground** to send a test payment using your new key without setting up a real checkout. Or follow the [Quickstart](/docs/getting-started) to call the API directly. ### Start accepting payments [#start-accepting-payments] Wire your backend to [`POST /v1/auth/token`](/docs/api-reference/payment-api/create-access-token) and [`POST /v1/payments/initiate`](/docs/api-reference/payment-api/initiate-payment), and point your webhook URL at a handler that listens for [`payment.status_updated`](/docs/api-reference/payment-api/webhook-events/payment-status-updated). Follow the [Accept a payment](/docs/guides/accept-a-payment) guide for the full end-to-end flow.
# Dashboard (/docs/saas-dashboard) The ARYZE Open Finance Payments Dashboard is where you set up everything the Payment API needs to work: your organisation, KYB verification, bank destinations, API keys, and webhook URLs. It's also where you monitor transactions and invoices day-to-day. ## Environments [#environments] The dashboard exposes two environments side by side: * **Sandbox** — for development. No real money moves. Minimal verification required. Use this while you integrate. * **Live** — real payments. Requires 2FA on your account, an approved KYB submission, and at least one configured bank destination. API keys are scoped to a single environment. Switch environments using the selector in the dashboard header; each environment has its own API keys, destinations, and transactions. # Sandbox (/docs/saas-dashboard/sandbox) The sandbox is a full working copy of the Payment API and dashboard. Nothing moves real money — it's there so you can build and rehearse before going live. ## What's the same as live [#whats-the-same-as-live] * Every Payment API endpoint, request shape, and response field. * Webhook delivery to your webhook URL, with the same retry behaviour. * Dashboard pages (transactions, reports, destinations, API keys). ## What's different [#whats-different] * API keys, destinations, and transactions are separate from live — a sandbox key can't create live payments and vice versa. * No KYB approval is required to create sandbox keys. * No funds ever move. "Completed" sandbox payments are marked completed by a test harness. ## Send a sandbox payment [#send-a-sandbox-payment] ### Option 1 — Dashboard Playground [#option-1--dashboard-playground] **Open Finance → Playground** has a built-in form that calls `/v1/payments/initiate` on your behalf and lets you step through each status transition. Useful for: * Testing your webhook handler end-to-end. * Triggering failure scenarios (`FAILED` status, timeouts). * Demoing the flow to non-technical teammates. Prerequisites: * A sandbox bank destination configured. * An active sandbox API key. ### Option 2 — Call the API from your own code [#option-2--call-the-api-from-your-own-code] Use your sandbox `client_secret` with the same base URL as live () — the `environment` returned in the token response tells you which side you're on. Follow the [Quickstart](/docs/getting-started) exactly. # Transactions (/docs/saas-dashboard/transactions) Every payment created through the API — by your backend, by the sandbox simulator, or by another tool using the same API key — appears in **Open Finance → Transactions**. ## The list view [#the-list-view] The table shows the most recent payments with: * Transaction ID (the same one the API returns). * Merchant reference. * Amount and currency. * Current status — see [Status values](#status-values) below. * Destination bank. * Initiation and last-updated timestamps. Filter by status, date range, destination, or free-text search against reference and transaction ID. ## Status values [#status-values] The status filter at the top of the list also offers `CANCELLED`, but no transaction ever sits in that state — Mastercard `CANCELLED` is recorded on the transaction's Mastercard status while the ARYZE status is set to `FAILED`. Filter by `FAILED` and check the Mastercard status column to find cancellations. ## The detail view [#the-detail-view] Click any row to see: * The Mastercard payment ID and raw Mastercard status — useful when opening a support ticket. * The destination bank, the API credential that initiated the payment, and the redirect / flow URLs issued to the customer. * The metadata you passed at initiation, decoded and rendered. For an authoritative state check, call [`GET /v1/payments/{transactionId}`](/docs/api-reference/payment-api/get-payment). The dashboard reflects the same record. ## Investigating a failed payment [#investigating-a-failed-payment] 1. Open the transaction. 2. Look at the **Mastercard status** — that's the raw upstream reason (for example `CANCELLED` or `REJECTED`). 3. Compare against your own webhook log. ARYZE retries failed deliveries up to four times (1s / 5s / 15s); if your endpoint never returned 2xx, the transaction status in the dashboard is still authoritative. 4. Still unclear? Contact support with the **ARYZE transaction ID** and **Mastercard payment ID**. ## Reconciling [#reconciling] For bulk reconciliation, compare transaction IDs and final status against your own order table. Everything in the dashboard maps 1:1 to what [`GET /v1/payments/{transactionId}`](/docs/api-reference/payment-api/get-payment) returns, so you can automate reconciliation with the API and keep the dashboard for ad-hoc investigation. # Verification (KYB) (/docs/saas-dashboard/verification) ARYZE is a regulated financial service. Before you can accept real payments, your business needs to pass **Know Your Business (KYB)** verification. KYB happens in the dashboard under **Account → Verification**. You can keep integrating in **sandbox** while KYB is under review. Only the live environment is gated behind approval. ## What you'll need [#what-youll-need] * Company registration number and country of incorporation * A PDF of your certificate of incorporation (or equivalent) * Details of each beneficial owner holding ≥25%: full name, date of birth, residential address, and a government-issued ID * Proof of operating address (utility bill or bank statement dated within the last 3 months) * A high-level description of your business model, expected monthly payment volume, and target geographies ## How to submit [#how-to-submit] ### Open the verification centre [#open-the-verification-centre] Go to **Account → Verification**. You'll see the status of your KYB submission and any outstanding items. ### Start the KYB assessment [#start-the-kyb-assessment] Click **Start KYB**. You're taken through the assessment step by step — business details first, then beneficial owners, then document upload. You can pause and resume. Your progress is saved per organisation. ### Submit for review [#submit-for-review] Once every section is complete, click **Submit**. You'll get an email when the review finishes. Typical turnaround is a few business days. ### Respond to follow-ups [#respond-to-follow-ups] If reviewers need more information — clearer documents, a missing owner — you'll get a notification in the dashboard. Re-open the verification centre to address each item and resubmit. ## Status values [#status-values] The badge in the dashboard reflects the underlying KYB record. The label you see maps to a single internal status code (shown in `code`). # Create access token (/docs/api-reference/payment-api/create-access-token) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get a payment (/docs/api-reference/payment-api/get-payment) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Payment API (/docs/api-reference/payment-api) The Payment API is served from a single base URL and versioned under `/v1`: All endpoints except [`POST /v1/auth/token`](./create-access-token) require a Bearer JWT in the `Authorization` header. Exchange your API key's client secret for a token at the auth endpoint, cache it for up to 60 minutes, and send it as `Authorization: Bearer ` on every other call. ## Rate limits [#rate-limits] | Endpoint | Policy | Limit | | ---------------------------------------------------------------- | --------- | --------------------- | | `POST /v1/auth/token` | `token` | 10 requests / minute | | `POST /v1/payments/initiate`, `GET /v1/payments/{transactionId}` | `payment` | 100 requests / minute | Exceeding a limit returns `429 Too Many Requests`. ## Conventions [#conventions] * **Content type:** every request and response body is `application/json`. * **Timestamps:** ISO 8601, UTC (`2026-04-21T14:30:00Z`). * **Amounts:** decimal numbers in the payment currency (`100.50`, not `10050` minor units). * **Currency:** ISO 4217 code (`EUR`, `GBP`, …). * **Environment:** the same base URL serves sandbox and live — the environment is determined by the API key you authenticate with and is echoed back in the token response. # Initiate a payment (/docs/api-reference/payment-api/initiate-payment) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Webhook Events (/docs/api-reference/payment-api/webhook-events) Whenever a payment's state changes, ARYZE POSTs a JSON body to the webhook URL on the API key you authenticated with. Each page below documents one event type — its payload, retry semantics, and an example handler. ## Delivery semantics [#delivery-semantics] * **Method:** `POST` with `Content-Type: application/json`. * **Success:** any HTTP 2xx within 10 seconds. * **Retries:** 1 initial delivery + 3 retries at 1 s, 5 s, 15 s. * **Signature verification:** payloads are not signed today. Cross-check every event by fetching the transaction from [`GET /v1/payments/{transactionId}`](/docs/api-reference/payment-api/get-payment) before acting on it. See [Consume webhooks](/docs/guides/consume-webhooks) for an end-to-end handler with idempotency and error handling. # Payment status updated (/docs/api-reference/payment-api/webhook-events/payment-status-updated) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}