Start building
Integrate SafeHaven
Connect a private SafeHaven OAuth app, accept NGN transfers and use banking, bills, identity and USSD services.
What is available
SafeHaven is available for Nigerian naira (NGN). Veyqo’s standard payment and hosted-checkout endpoints collect bank transfers through short-lived, fixed-amount virtual accounts. Amounts enter and leave Veyqo in kobo: 50000 means NGN 500.00.
The provider extension API adds accounts and statements, individual/corporate subaccounts, virtual-account management, bank lists, name enquiry, outbound transfers, beneficiaries, airtime, data, cable TV, utility payments, BVN/NIN verification and USSD references. Your SafeHaven app needs the corresponding permissions and product entitlements.
The published SafeHaven reference does not define a refund or direct-debit API. Veyqo does not advertise refunds, saved-card subscriptions or direct debits for this connection. An outbound transfer is a separate debit, not a normalized refund.
| Use case | Veyqo surface |
|---|---|
| Accept a bank transfer | POST /payments or POST /checkout/sessions with provider: safehaven |
| Banking, bills, identity and USSD | /api/v1/safehaven/* provider extension endpoints |
| Run an operation in the dashboard | Providers → SafeHaven connection → Open services |
| Find an uncertain write | GET /safehaven/operations?idempotency_key=... |
| Receive normalized collection events | payment.* events on the original project webhook |
| Receive service notifications | Subscribe to provider_service.updated |
Connect your account
- 1Create a sandbox account at online.sandbox.safehavenmfb.com. Use the production SafeHaven portal only when you are ready for real money.
- 2In Settings → OAuth Apps, create a private app with the READ, WRITE and PAY permissions your integration needs. Upload your RSA public certificate and keep its matching private key securely.
- 3In Veyqo, select the project and sandbox environment, then connect SafeHaven. Enter the OAuth client ID, PEM private key and company URL (JWT issuer). Click Load accounts and select the settlement account by name and number. Veyqo fills its internal _id and account number automatically. Loading accounts does not save credentials; Connect saves them after verification.
- 4Veyqo signs short-lived RS256 assertions, exchanges them for OAuth tokens, and checks that the account ID and account number match. You do not paste temporary access or refresh tokens into Veyqo.
- 5Choose a dedicated provider account or explicitly share it with other projects in the same organization and environment type. Sandbox and live credentials never mix.
- 6Enable webhooks in SafeHaven Settings → Notification Settings. Paste Veyqo’s provider-account webhook URL. Veyqo supplies the same URL when creating virtual accounts. Configure your own outgoing Veyqo webhook separately.
Accept a payment
Create a payment or hosted session on your server with provider set to safehaven. A direct payment’s data.checkout_url is a Veyqo-hosted page displaying the transfer account. A hosted session returns data.url as usual.
The generated account accepts the exact amount and expires after 15 minutes. Veyqo uses its own globally unique payment ID as the upstream reference. Save the returned payment/session ID and verify status, amount, currency and order ownership before fulfilment.
No SafeHaven secret is exposed to the checkout page. The hosted page displays bank-transfer instructions; USSD service operations are available separately through the extension API.
curl --request POST 'https://api.veyqo.org/api/v1/checkout/sessions' \
--header "Authorization: Bearer $VEYQO_SECRET_KEY" \
--header "Idempotency-Key: $ORDER_CHECKOUT_KEY" \
--header 'Content-Type: application/json' \
--data '{"provider":"safehaven","amount":50000,"currency":"NGN","reference":"order-1001","customer":{"email":"customer@example.com"}}'Grant service permissions explicitly
Existing payment permissions do not authorize outbound transfers or bill payments. An organization owner or admin must grant account, transfer, bill-payment and identity scopes; a developer cannot grant them to themselves. Create a scoped key for the service you need. Write permissions imply the corresponding read permission. Operation lookup also requires events:read plus the read permission for the original service.
| Scope | Allows |
|---|---|
| payments:write / payments:read | Collections, checkout, USSD references and checkout verification |
| accounts:write / accounts:read | Bank accounts, subaccounts, virtual accounts, balances and statements |
| transfers:write / transfers:read | Outbound transfers, beneficiary management, name enquiry and transfer status |
| bills:write / bills:read | VAS purchases, service catalogues, recipient verification and transaction lookup |
| identity:write / identity:read | Initiate and validate identity checks; read their operation records |
| events:read | Read normalized events and look up provider operation records |
Make a transfer safely
- 1List banks with GET /safehaven/transfers/banks.
- 2Call POST /safehaven/transfers/name-enquiry with bank_code and account_number. Save the returned session_id; Veyqo binds it to this project and the exact destination.
- 3Call POST /safehaven/transfers with name_enquiry_reference, beneficiary_bank_code, beneficiary_account_number, integer amount in kobo, and save_beneficiary. Persist one Idempotency-Key before sending.
- 4The configured settlement account is the default debit source. Another debit_account_number must be a bank account created in this project environment. A name enquiry for a different beneficiary is rejected.
- 5Save data.operation_id and the returned provider session_id. Created or pending means the request is not settled. Use POST /safehaven/transfers/status to reconcile, and process provider_service.updated notifications.
- 6If the request times out, look up the same idempotency key using GET /safehaven/operations. Unknown transfers are queried by their Veyqo-generated upstream reference. The read does not resubmit the transfer.
Bills and identity verification
For bills, discover services, categories and products at runtime. Verify the meter or cable account using /safehaven/vas/verify before purchasing where the provider requires it. Do not hard-code product IDs or assume every product is enabled for your account.
Purchase airtime, data, cable TV or utilities with the appropriate bills:write endpoint. Amount is kobo; Veyqo converts it to naira. The configured account is the default debit source. Save the operation and resource IDs and check the actual provider status.
Initiate BVN/NIN verification at POST /safehaven/identity. Validate the resulting identity_id and customer OTP at POST /safehaven/identity/validate, then use that project-owned identity for a subaccount where required. These checks can charge the bank account.
Corporate subaccounts require the provider’s director verification and company registration requirements. Individual identity types and conditional OTP/identity requirements follow the chosen SafeHaven verification method.
Identity results are encrypted at rest. Generic logs redact identity numbers, OTPs and images. Notifications contain resource identifiers and status, not the identity report. Do not copy sensitive identity results into application logs.
Shared credentials and project routing
Veyqo generates the upstream reference for supported writes. Your external_reference or payment_reference is preserved as merchant_reference in the response; it never chooses a project. A service operation returns a pso_ operation_id.
Each owned account, verification, enquiry, transaction and operation record is bound to its original project environment and provider connection. Resource reads stay pinned to that connection after an account switch. IDs from another project return RESOURCE_NOT_FOUND.
The configured root account balance is pooled across projects sharing that bank account; it is not a per-project ledger. Use project-owned subaccounts for isolated balances. Account lists exclude unowned subaccounts. Transfer and bill history contain only locally initiated project records, with one-based page/per_page pagination and status/date filters. History remains readable after disconnecting a provider. Beneficiary lists include only destinations used by this project. Statements on a shared root account are refused; use a project-owned subaccount.
Account-level notifications for a shared settlement account cannot identify a project reliably. Unmatched notifications are ignored. Incoming credit to a project-created subaccount and outgoing transfers created by Veyqo can be routed through their stored ownership. Other services without a published webhook contract must be polled.
Webhook trust and event payloads
SafeHaven’s public webhook documentation specifies event bodies but no signature scheme. Veyqo treats the notification as a hint, authenticates to SafeHaven and fetches the transaction before accepting a collection or transfer state change. No invented HMAC header or secret is required.
A failed provider verification does not settle a payment. Provider outages leave the delivery retryable. Collection success is verified again against the stored payment amount and currency before emitting payment.succeeded.
Subscribe to provider_service.updated for service resource creation or a verified status change. Its data contains operation_id, service, kind, resource_id, provider and provider status; monetary resources may include amount and currency. The event is not a payment.* payload. An accepted request can still be pending.
Your outgoing Veyqo webhook keeps the same raw-body HMAC signature verification, replay tolerance and event-ID deduplication as other Veyqo events.
SafeHaven webhook referenceTroubleshooting
| Problem | Action |
|---|---|
| PROVIDER_AUTHENTICATION_FAILED | Check the environment, private OAuth app, client ID, PEM key, issuer URL and SafeHaven app permissions. |
| Settlement account mismatch | Use the account _id and its matching ten-digit NUBAN. These are different identifiers. |
| VALIDATION_FAILED | Use snake_case request fields, positive integer kobo amounts and the documented product-specific required fields. Callback URLs are owned by Veyqo. |
| RESOURCE_NOT_FOUND | Use a resource from the same project environment. Create or list the resource here before using it. |
| INSUFFICIENT_PERMISSION | Grant the explicit service scope. Shared root-account statements require a project-owned subaccount. |
| PROVIDER_REQUEST_FAILED | Check SafeHaven product activation, KYC, balance, transaction limits and service-specific response details. |
| IDEMPOTENCY_KEY_REUSED | Keep one key and unchanged payload per operation. Use a new key only for a deliberately new operation. |
| IDEMPOTENCY_IN_PROGRESS | The first write is pending or uncertain. Look up the original key and reconcile with SafeHaven. Do not blindly debit again. |
| Provider timeout or outage | Retry reads after backoff. Reconcile writes by operation ID/key before sending another write. |