# Integrating Veyqo Veyqo connects a merchant's existing payment provider accounts through one API. Public API base: https://api.veyqo.org/api/v1 Documentation: https://veyqo.org/docs Full integration context: https://veyqo.org/llms-full.txt - Use Authorization: Bearer $VEYQO_SECRET_KEY on your server only. - API keys, project assignments and outgoing webhooks belong to one project environment. - Provider credential accounts may be dedicated or shared across projects in the same organization and environment type. Switch assignments for new payments; keep existing payments and refunds tied to their original account. - Start in sandbox. Use provider test credentials with your sandbox key. - Amounts are integers in the smallest currency unit: 50000 NGN is NGN 500.00. - Calculate prices from trusted server-side order data, never browser input. - POST /payments creates a payment; data.checkout.url is its checkout URL. - POST /checkout/sessions creates hosted checkout; data.id is the session ID. - Persist an Idempotency-Key per logical POST operation. Reuse it with the same body on retries; use a new key for a different operation. - Use GET /payments/{id} or POST /payments/{id}/verify to check a payment. - Verify requires payments:read; create requires payments:write. - Webhooks use Veyqo-Signature and Veyqo-Timestamp headers. - Verify HMAC-SHA256(secret, timestamp + '.' + raw request body), compare in constant time, accept any matching comma-separated signature during rotation, and reject timestamps more than 300 seconds from now. - Deduplicate events by event ID and make fulfilment idempotent. - Fulfil only after trusted server confirmation of succeeded status and a match on the stored order reference, amount and currency. A redirect is not proof. - POST /payments/{id}/refunds supports full or partial refunds (refunds:write). - Responses use { success, data } or { success: false, error: { code, message, details } }. - Check response status and success before reading data. Respect Retry-After. - Never log API secrets, invent SDK packages, or assume an MCP server exists. - Read the current API reference for endpoint fields and supported capabilities. - Add tests for duplicate events, invalid signatures, amount mismatches and retries. # Build once. Connect every provider. Your payment providers, behind one API. Start with a sandbox payment and take the same integration live. ## How Veyqo works Veyqo is a payment orchestration layer. Connect your own provider accounts, create payments through one API, and receive normalized events. Your connected provider processes the payment. An organization contains projects. Each project has separate sandbox and live environments. API keys, provider credentials, payments and webhook endpoints are scoped to an environment. 1. Connect a provider using the credentials from your own provider account. 2. Your server creates a payment or hosted checkout session with a Veyqo API key. 3. The customer completes checkout using the selected provider. 4. Veyqo verifies the provider result and sends a signed event to your server. ## Dedicated or shared provider accounts Connect a new provider account for separate credentials, or choose Use an existing account to reuse a shared or unassigned account. Sharing is optional and stays within the same organization; sandbox and live accounts remain separate. Each provider account has one incoming webhook URL. Veyqo identifies the payment and delivers its event only to the owning project’s outgoing webhook endpoints. Projects may use identical order references because Veyqo sends its own unique payment ID to the provider. Use Switch account to change the account for new payments. Existing payments, refunds and late events stay tied to their original assignment. Updating credentials rotates the keys for every project using the account; disconnecting detaches only the selected project. ## Choose your integration - [API integration](https://veyqo.org/docs/payments): Create a payment on your server and redirect to its checkout URL. - [Hosted checkout](https://veyqo.org/docs/checkout): Create a checkout session and open it with the browser checkout script. - [Build with an AI agent](https://veyqo.org/docs/ai-agents): Give Claude, Codex or another assistant the integration context. ## Before you start Create a Veyqo account, choose a project, switch to sandbox and connect a provider with test credentials. Then create an API key with the scopes your server needs. > Veyqo API keys authenticate your application to Veyqo. Provider credentials connect Veyqo to your payment provider. Keep both on the server. --- # Your first payment Go from a connected sandbox provider to a payment your server can verify. ## 1. Set up your sandbox 1. Create an account and a project in the dashboard. 2. Select the sandbox environment and connect a supported provider using its test credentials. 3. Create an API key with payments:write and payments:read. Store the secret in your server environment as VEYQO_SECRET_KEY. 4. Create and persist a payment-attempt key for your order. Use it as ORDER_PAYMENT_KEY in the request below. ## 2. Create a payment Run this request from your server. The example charges NGN 500.00. Use a fresh order reference for each new order and a stable idempotency key for retries of the same operation. ``` curl https://api.veyqo.org/api/v1/payments \ -H "Authorization: Bearer $VEYQO_SECRET_KEY" \ -H "Idempotency-Key: $ORDER_PAYMENT_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 50000, "currency": "NGN", "reference": "ORDER-1001", "provider": "paystack", "customer": { "email": "customer@example.com" }, "metadata": { "order_id": "1001" } }' ``` ## 3. Send the customer to checkout A successful response has success: true and a data object. Save data.id with your order, then redirect the customer to data.checkout.url. Check the HTTP status and success flag before using these fields. ``` { "success": true, "data": { "id": "pay_example", "reference": "ORDER-1001", "status": "pending", "amount": 50000, "currency": "NGN", "checkout": { "url": "https://checkout.example.com/session" } } } ``` ## 4. Confirm the result on your server Configure a webhook endpoint in the same sandbox environment and subscribe to payment.succeeded. Verify each signature before reading the event, then match the confirmed payment to your stored order. Use the verify endpoint when you need to ask the provider for the latest result. ``` curl -X POST https://api.veyqo.org/api/v1/payments/pay_example/verify \ -H "Authorization: Bearer $VEYQO_SECRET_KEY" ``` > Fulfil only after trusted server confirmation of succeeded status, amount, currency and order reference. A browser redirect or callback is not proof of payment. --- # Keys & authentication Authenticate server requests and keep sandbox and live traffic separate. ## Use a server API key Create a key in Dashboard → API keys. The secret is shown once. Store it in a server environment variable and send it as a Bearer token on every public API request. The public API uses /api/v1. Dashboard session authentication is separate and is not used for merchant integrations. ``` GET https://api.veyqo.org/api/v1/payments Authorization: Bearer Content-Type: application/json ``` ## Grant the scopes you need | Operation | Required scope | | --- | --- | | Create payments or checkout sessions | payments:write | | Read or verify payments; list providers | payments:read | | Create refunds | refunds:write | | Read refunds | refunds:read | | Read normalized events | events:read | ## One key, one environment A sandbox key can access its sandbox resources; a live key can access its live resources. Connect the provider and configure webhooks separately for each environment. A key with the right scope still cannot cross the environment boundary. > Never place a secret API key in browser code, a public repository, a prompt, or client-side VITE_ variables. The checkout script’s publishable key is a different credential. --- # Create & verify payments Initialize a charge, direct the customer to checkout, and confirm what happened. ## Create on your server POST /payments starts a payment through a connected provider. The provider field selects a provider; its connection must belong to the key’s environment. Calculate the amount from trusted order data. ### cURL ``` curl https://api.veyqo.org/api/v1/payments \ -H "Authorization: Bearer $VEYQO_SECRET_KEY" \ -H "Idempotency-Key: $ORDER_PAYMENT_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 50000, "currency": "NGN", "reference": "ORDER-1001", "provider": "paystack", "customer": { "email": "customer@example.com" }, "metadata": { "order_id": "1001" } }' ``` ### Node.js ``` // Server-side JavaScript (Node.js 18+). Values come from your order database. const response = await fetch('https://api.veyqo.org/api/v1/payments', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VEYQO_SECRET_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': order.paymentAttemptKey, // Persist before the request. }, body: JSON.stringify({ amount: order.totalMinor, currency: order.currency, reference: order.reference, provider: 'paystack', customer: { email: order.customerEmail }, }), }); const result = await response.json(); if (!response.ok || !result.success) { throw new Error(result.error?.code ?? 'PAYMENT_REQUEST_FAILED'); } // Persist result.data.id against the order before redirecting the customer. const checkoutUrl = result.data.checkout.url; ``` ## Payment request fields | Field | Meaning | | --- | --- | | amount | Required positive integer in the smallest currency unit. | | currency | Required three-letter currency code, supported by the provider. | | customer.email | Required customer email for the nested customer form. | | reference | Your order reference; if omitted, the payment ID becomes the reference. | | provider | Provider slug, such as paystack. | | callback_url | Optional HTTPS URL the provider can return the customer to. | | metadata | Optional application context, such as an internal order ID. | ## Retry the same operation safely Send an Idempotency-Key on POST requests. Generate and persist it before the first attempt. If the request times out, retry with the same key and exactly the same body. A different body with the same key returns a conflict. Use a new key for a new logical operation. Your order reference and idempotency key serve different purposes: one identifies the order; the other identifies a request attempt across retries. ## Verify before fulfilment GET /payments/{id} retrieves the stored payment. POST /payments/{id}/verify queries the provider and updates the payment result. Both require payments:read. Pending is not a successful payment. Store the payment ID and reconcile webhooks against the order’s reference, expected amount and currency. Deduplicate fulfilment even if the same confirmation arrives more than once. --- # Hosted checkout Create checkout on your server, then open it from your website. ## Create a checkout session Use POST /checkout/sessions with a server key that has payments:write. The session fixes the amount on the server. Save the returned data.id against the order. ``` curl https://api.veyqo.org/api/v1/checkout/sessions \ -H "Authorization: Bearer $VEYQO_SECRET_KEY" \ -H "Idempotency-Key: $ORDER_PAYMENT_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 50000, "currency": "NGN", "reference": "ORDER-1001", "provider": "paystack", "customer": { "email": "customer@example.com" }, "success_url": "https://shop.example.com/thanks", "cancel_url": "https://shop.example.com/cart" }' ``` ## Open checkout in the browser Expose your own authenticated /create-checkout endpoint that looks up the order, creates the session and returns { id: result.data.id }. Check that the signed-in customer owns the order. ``` ``` > Use signed webhooks to fulfil orders. Treat the browser callback as a user experience signal and show a pending state until your server confirms payment. --- # Receive trusted payment events Verify the sender, reject replays, and handle duplicate deliveries safely. ## Register your endpoint In Dashboard → Webhooks, create a public HTTPS endpoint in the same environment as your payments and subscribe to payment.succeeded. Store its signing secret as VEYQO_WEBHOOK_SECRET on your server. The API key and webhook signing secret are different secrets. Verify incoming deliveries with the webhook endpoint’s signing secret. ## Verify the raw request body Veyqo-Signature is a hexadecimal HMAC-SHA256 digest of {timestamp}.{raw body}. Veyqo-Timestamp provides the timestamp. During secret rotation, the signature header contains comma-separated signatures; accept a match with your configured secret. Verify in constant time and reject timestamps outside a five-minute window. The following Express example shows signature verification and the boundary where you must add your own durable event persistence. It is not a complete order processing service. ``` import express from 'express'; import { createHmac, timingSafeEqual } from 'node:crypto'; const app = express(); // Register BEFORE app.use(express.json()); signatures need the original bytes. app.post('/webhooks/veyqo', express.raw({ type: 'application/json' }), async (req, res) => { const signature = req.get('Veyqo-Signature') ?? ''; const timestamp = req.get('Veyqo-Timestamp') ?? ''; const secret = process.env.VEYQO_WEBHOOK_SECRET; if (!secret) return res.sendStatus(503); if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300 || !signature) return res.sendStatus(401); const expected = createHmac('sha256', secret) .update(timestamp + '.').update(req.body).digest(); // During secret rotation, accept any matching comma-separated signature. const valid = signature.split(',').map(value => value.trim()).some(value => /^[a-f0-9]{64}$/i.test(value) && timingSafeEqual(expected, Buffer.from(value, 'hex')) ); if (!valid) { return res.sendStatus(401); } let event; try { event = JSON.parse(req.body.toString('utf8')); } catch { return res.sendStatus(400); } // Implement this with a durable queue/outbox and UNIQUE(event.id). // Acknowledge only after durable storage; retry storage failures with 5xx. try { await persistEventOnce(event); return res.sendStatus(200); } catch { return res.sendStatus(503); } }); // Your worker handles payment.succeeded. Resolve its payment_id via the API, // match status, reference, amount and currency to the stored order, then // atomically mark the order paid once. persistEventOnce is your app's function. ``` ## Process once, even when delivered again 1. Verify the signature and timestamp before trusting the payload. 2. Persist the event durably with a unique constraint on event.id. A duplicate delivery should receive 2xx without repeating work. 3. Acknowledge promptly after storage and process the order in a worker. 4. For payment.succeeded, resolve data.payment_id and verify the payment against your order’s reference, amount and currency. 5. Update the order atomically so repeated events cannot fulfil it twice. > Return a non-2xx response if you could not persist the event. Returning 200 before durable storage can lose an event if the process stops. ## Inspect and recover deliveries Use Events and Deliveries in the dashboard to investigate failures and retry a delivery. Veyqo retries failed deliveries with backoff. Your handler must tolerate duplicates and must not depend on event arrival order. The events API also lets your server read normalized events using events:read, so missed notifications can be reconciled. --- # Refund a payment Issue a full or partial refund through the provider that processed the payment. ## Create the refund Use a server key with refunds:write. Omit amount for the remaining refundable balance, or supply an integer in minor units for a partial refund. Persist a distinct idempotency key for the refund operation. Provider support and payment state determine whether the refund can be made. ``` curl https://api.veyqo.org/api/v1/payments/pay_example/refunds \ -H "Authorization: Bearer $VEYQO_SECRET_KEY" \ -H "Idempotency-Key: $REFUND_ATTEMPT_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 25000, "reason": "requested_by_customer" }' ``` ## Track the result Save the returned refund ID and inspect its status. Retrieve it with GET /refunds/{id} using refunds:read. Do not tell the customer a refund is complete until its status confirms completion. --- # Build with your AI coding agent Give your assistant the API contract and payment rules before it writes the integration. ## Give your agent the docs Use the downloadable integration context below with Claude Code, Codex or another coding assistant. For chat tools, attach the Markdown file or paste its contents. For tools that can read URLs, ask them to read /llms-full.txt first. These files provide documentation context. They do not install an SDK, enable an MCP server, or give an assistant access to your Veyqo account. - [Documentation index](/llms.txt): A small index of the guides for tools that consume llms.txt. - [Full integration context](/llms-full.txt): All integration guides in one plain-text Markdown document. ## Claude Code Merge the integration notes into your project’s CLAUDE.md, or save them in docs/veyqo.md and reference that file from CLAUDE.md. Keep your existing project instructions. Ask Claude to inspect the application before choosing where the integration belongs. ``` # Veyqo integration Read docs/veyqo.md before changing payment code. Keep API keys server-side. Calculate totals from stored order data. Verify signed webhooks and make order fulfilment idempotent. Run the project’s existing payment tests after changes. ``` [Claude Code project instructions](https://code.claude.com/docs/en/memory) ## Codex Merge the same notes into AGENTS.md in your project root, or point that file to docs/veyqo.md. Codex reads repository instructions before working. Add the test command and payment code locations used by your project. ``` # Payment integration instructions Before changing payments, read docs/veyqo.md. Use the Veyqo REST API; do not invent SDK package names. Keep credentials in server environment variables. Verify signatures using the raw request body. Test duplicate events, retries and amount mismatches. Run this repository’s existing checks before finishing. ``` [Codex repository instructions](https://learn.chatgpt.com/docs/agent-configuration/agents-md) ## Start with a concrete integration prompt This prompt also works with other coding assistants when you attach the integration context. Replace the application-specific details with your own stack and order model. ``` Read https://veyqo.org/llms-full.txt and inspect this application's stack. Integrate Veyqo hosted checkout using our existing server and order model. Create the checkout session server-side with a sandbox secret from the environment. Use the stored order total in minor units, currency and reference. Add a signed webhook handler with raw-body signature validation, a five-minute replay window, event deduplication and idempotent order fulfilment. Verify payment status, amount, currency and reference before fulfilling an order. Handle API errors and reuse a persisted idempotency key on retries. Keep secrets server-side and add meaningful tests. Tell me which dashboard settings and environment variables I need to configure. Do not deploy. ``` ## Reusable integration context Save this as docs/veyqo.md. Review the generated code and test it in sandbox before using live credentials. ``` # Integrating Veyqo Veyqo connects a merchant's existing payment provider accounts through one API. Public API base: https://api.veyqo.org/api/v1 Documentation: https://veyqo.org/docs Full integration context: https://veyqo.org/llms-full.txt - Use Authorization: Bearer $VEYQO_SECRET_KEY on your server only. - API keys, project assignments and outgoing webhooks belong to one project environment. - Provider credential accounts may be dedicated or shared across projects in the same organization and environment type. Switch assignments for new payments; keep existing payments and refunds tied to their original account. - Start in sandbox. Use provider test credentials with your sandbox key. - Amounts are integers in the smallest currency unit: 50000 NGN is NGN 500.00. - Calculate prices from trusted server-side order data, never browser input. - POST /payments creates a payment; data.checkout.url is its checkout URL. - POST /checkout/sessions creates hosted checkout; data.id is the session ID. - Persist an Idempotency-Key per logical POST operation. Reuse it with the same body on retries; use a new key for a different operation. - Use GET /payments/{id} or POST /payments/{id}/verify to check a payment. - Verify requires payments:read; create requires payments:write. - Webhooks use Veyqo-Signature and Veyqo-Timestamp headers. - Verify HMAC-SHA256(secret, timestamp + '.' + raw request body), compare in constant time, accept any matching comma-separated signature during rotation, and reject timestamps more than 300 seconds from now. - Deduplicate events by event ID and make fulfilment idempotent. - Fulfil only after trusted server confirmation of succeeded status and a match on the stored order reference, amount and currency. A redirect is not proof. - POST /payments/{id}/refunds supports full or partial refunds (refunds:write). - Responses use { success, data } or { success: false, error: { code, message, details } }. - Check response status and success before reading data. Respect Retry-After. - Never log API secrets, invent SDK packages, or assume an MCP server exists. - Read the current API reference for endpoint fields and supported capabilities. - Add tests for duplicate events, invalid signatures, amount mismatches and retries. ``` --- # Errors & retries Understand failures and retry requests without repeating a charge. ## Read the error envelope Check the HTTP status and success flag before using data. Validation details identify fields that need correction. Keep X-Request-Id when contacting support; never include your secret API key. ``` { "success": false, "error": { "code": "VALIDATION_FAILED", "message": "The given data was invalid.", "details": { "amount": ["The amount must be a whole number."] } } } ``` ## Common errors | Code / status | What to do | | --- | --- | | INVALID_API_KEY · 401 | Check the Bearer token and whether the key was revoked. | | INSUFFICIENT_PERMISSION · 403 | Grant the required endpoint scope. | | ENVIRONMENT_MISMATCH · 403 | Use the key for the resource’s environment. | | PAYMENT_NOT_FOUND · 404 | Check the payment ID and environment. | | DUPLICATE_REFERENCE · 409 | Look up the existing order/payment before trying again. | | IDEMPOTENCY_KEY_REUSED · 409 | Use the original body for a retry; a new operation needs a new key. | | VALIDATION_FAILED · 422 | Correct the fields in error.details. | | PROVIDER_NOT_CONNECTED · 422 | Connect the provider in this environment. | | RATE_LIMIT_EXCEEDED · 429 | Wait for Retry-After before retrying. | | PROVIDER_UNAVAILABLE · 503 | Retry with backoff and the same persisted idempotency key. | ## Recover from timeouts A timeout does not prove the provider did nothing. Inspect the stored payment when possible and retry the same operation with the same idempotency key and request body. Use bounded exponential backoff for transient errors and avoid retrying validation or authentication failures unchanged. --- # Take your integration live Move the tested integration into a separately configured live environment. ## Finish sandbox verification 1. Exercise successful, pending, failed and abandoned payment paths. 2. Confirm invalid webhook signatures are rejected. 3. Replay an event and confirm the order is fulfilled only once. 4. Test retries with the same idempotency key and order amount mismatches. 5. Check that API failures show a useful message without exposing secrets. ## Configure live independently 1. Verify your Veyqo account email and complete the provider’s own live activation requirements. 2. Select the live environment and connect your provider’s live credentials. 3. Create a live API key with only the scopes your server requires. 4. Create a live HTTPS webhook endpoint and configure its signing secret on your server. 5. Set the live server environment variables and confirm return URLs point to your production website. 6. Complete a small real payment and verify its webhook, stored payment and order status. ## Monitor after launch Track payment failures and webhook delivery failures in the dashboard. Keep order-to-payment IDs for reconciliation and retain request IDs for troubleshooting. Rotate credentials through your normal secret management process. --- # API reference Endpoint definitions published by the Veyqo API, shared with the dashboard console. Fetch the live endpoint definitions at https://api.veyqo.org/docs/reference (JSON).