Integration Checklist
Before we can grant production API access, you'll need to complete the following tests to verify that your integration has been implemented correctly and behaves as expected.
The checklist below is intended to provide a comprehensive view of the capabilities available through the Lorum API and the endpoints that may be relevant to your integration.
You are not expected to implement every endpoint or flow listed. The functionality you choose to integrate will depend on your specific use case and how you intend to use the platform.
For certification, we'll review and validate the endpoints, flows, and webhooks that you have actually implemented. The purpose of the certification process is to confirm that the functionality you plan to use in production has been properly built, tested, and exercised in the sandbox environment.
Prerequisites
-
Define your API integration use case - a clear description of what you are building and the purpose it will serve.
-
Store credentials securely - client ID and client secret must be stored in environment variables, a secrets manager, or another secure storage mechanism. never hardcoded in source code or configuration files. → Authenticating to the API
-
Cache and reuse access tokens - access tokens are valid for 3 hours and should be cached and reused for the duration of their validity period. Do not request a new token for every API call. → Get a bearer token
-
Use idempotency keys on all POST & retries requests - every POST and retry request must include an
Idempotency-Keyheader. → Idempotency docs -
Verify idempotent behaviour - submit the same request multiple times using the same idempotency key and confirm that Lorum returns the original response without creating duplicate resources.
-
Enable webhooks in organisation settings before starting - for each webhook type you intend to consume, provide logs showing that the event was received and that a 2xx response was returned to Lorum. → Webhooks
Managing customers
-
Create a business customer - Add Business. Submit the request and HTTP 201 response including the returned customer ID.
-
Create an individual customer - Add Individual. Submit the request and HTTP 201 response including the returned customer ID.
-
Get a customer - Get Customer. Confirm the response returns the correct customer details.
-
Update a customer - Update Customer. Confirm customer details can be updated and the response reflects the changes correctly.
-
List customers - List Customers. Confirm your integration can retrieve and filter your customer list.
-
Add a shareholder to a business - Add Shareholder. Submit the request and HTTP 201 response including the returned shareholder ID.
-
List shareholders for a business - List Shareholders. Confirm shareholder records are retrievable for business customers.
-
Get a shareholder - Get Shareholder. Confirm individual shareholder records are retrievable.
-
Update a shareholder - Update Shareholder. Confirm shareholder details can be updated and the response reflects the changes correctly.
Currency onboarding
-
Upload customer documents - Upload Document. Upload required customer documentation to Lorum. Include both the request and the HTTP 201 response in your logs.
-
Link a document to a customer - Link Document. Associate an uploaded document with a specific customer record.
-
Get a document - Get Document. Confirm the uploaded document is retrievable and the response includes the correct status.
-
List documents - List Documents. Confirm all documents associated with a customer are returned correctly.
-
Unlink a document from a customer. - Unlink Document. Unlink a document from a customer.
-
Onboard a customer for a specific currency - Onboard Customer. Trigger onboarding for a customer for the selected currency (EUR, GBP, AED, USD).
-
Check onboarding status - Get Customer Onboarding Status. Check whether a customer has been successfully onboarded for a specific currency. Handle both the 200 (onboarded) and 404 (not yet onboarded) responses correctly.
-
onboarding_completedwebhook - your integration must wait for this event before proceeding to account creation. → Docs
The only exception is onboarding for AED, which is instant and does not rely on theonboarding_completedwebhook.
Managing virtual accounts
-
Create an account - Create Account. Create a virtual account for a customer. Include the
Idempotency-Keyheader in your request log to ensure safe retries. -
Get an account - Get Account. Retrieve account details and confirm the response includes account status and balances.
-
Update account data - Update Account. Confirm account name or external ID can be updated and the response reflects the changes.
-
List a customer's accounts - List a Customer's Accounts. Confirm all accounts for a given customer are returned correctly.
-
List all accounts for organisation - List All Accounts. Confirm organisation-wide account visibility works correctly.
-
Get an account's balance history - Get Balance History. Confirm balance changes over time are returned correctly.
-
Get account confirmation letter - Get Account Confirmation Letter. Confirm the letter is generated and returned correctly for an open account.
-
account_openedwebhook - surface the virtual account to users only after this event is received. → Docs -
account_updatedwebhook - Docs
Making payments
-
Estimate the fee for a payment - Estimate Payment Fee. Calculate the applicable fee for a payment before creating it and confirm your integration correctly handles real-time fee deduction.
Fees are automatically collected from the available balance of the transaction account at the time the payment is processed, as described here. This can also be configured to collect fees from the primary account instead. -
Initiate an outbound payment - Create Outbound Payment. Create an outbound payment request. Include the
Idempotency-Keyheader and capture the returned transaction ID. -
Get a transaction - Get a Transaction. Retrieve transaction details and validate validate all possible transaction states where applicable.
-
Get transaction confirmation letter - Get Transaction Confirmation Letter. Confirm that a confirmation letter can be generated for transactions in submitted or executed state.
-
List transactions for an account - List Transactions. Validate pagination handling by ensuring your integration processes
pages.nextand retrieves subsequent pages. -
Get all transactions across accounts - Get All Transactions. Confirm organisation-wide transaction visibility works correctly.
-
Link a document to a transaction - Link Document to Transaction. Confirm a supporting document can be attached to a transaction.
-
Get linked documents for a transaction - Get Linked Documents. Confirm linked documents are retrievable for a given transaction.
-
outbound_local_payment_createdwebhook - Docs -
outbound_local_payment_submittedwebhook - Docs -
outbound_local_payment_executedwebhook - Docs -
outbound_local_payment_failedwebhook - confirmfailure_reasonis captured. → Docs -
outbound_local_payment_returnedwebhook - confirmreturned_reasonis captured. → Docs -
inbound_local_payment_settledwebhook - Docs -
fee_debitedwebhook - confirm the fee is linked to the originating transaction viafee_target.reference. → Docs
This webhook is only applicable when fees are collected from the primary account.
Making internal transfers
-
Initiate an internal transfer - Move Funds Between Accounts. Create an internal transfer between two accounts. Include the
Idempotency-Keyheader and capture the returned transaction ID. -
Get a transaction - Get a Transaction. Retrieve transfer details and confirm both outbound and inbound direction of the transfer are visible.
-
List transactions for an account - List Transactions. Confirm both directions appear when listing transactions for the remitter and beneficiary accounts.
-
Get all transactions across accounts - Get All Transactions. Validate that both directions are visible at an organisation level.
-
internal_transfer_createdwebhook - Docs -
internal_transfer_settledwebhook - Docs -
inbound_internal_transfer_settledwebhook - Docs -
internal_transfer_failedwebhook - confirmfailure_reasonis captured. → Docs
Making foreign exchange
-
Get an exchange quote - Get Exchange Quote. Confirm your integration retrieves a rate before initiating a foreign exchange and handles the quote fields correctly.
-
Initiate an exchange transfer - Create a Currency Exchange. Create a currency exchange transaction. Include the
Idempotency-Keyheader and capture the returned transaction ID. -
Get a transaction - Get a Transaction. Retrieve transaction details and confirm
rate,currency, andcounterpart_currencyare included in the response. -
List transactions for an account - List Transactions. Confirm that FX transactions appear correctly when listing transactions for both the remitter and beneficiary accounts.
-
outbound_exchange_transfer_createdwebhook - Triggered when an FX is created.confirmrate,currency, andcounterpart_currencyare captured. → Docs -
outbound_exchange_transfer_executedwebhook - Docs -
inbound_exchange_transfer_settledwebhook - Docs -
outbound_exchange_transfer_failedwebhook - confirmfailure_reasonis captured. → Docs
Submission
Include the timestamp and timezone of your tests alongside:
- A YAML file that contains:
- A short written overview of your API integration use-case, token management and idempotency approach
- API request and response logs for all relevant endpoints within your integration scope (sensitive data masked)
- Webhook logs confirming receipt and a 2xx response for each event
YAML Submission Template
# ============================================================
# Lorum Integration Submission Template
# ============================================================
# Complete all required fields and return this file to your
# Lorum contact. Mask sensitive values with "***".
# Fields marked (required) must be present for review to begin.
# ============================================================
metadata:
client_name: # (required) e.g. "Acme Payments Ltd"
submitted_by: # (required) name and email of submitter
submission_date: # (required) ISO 8601 e.g. "2026-07-21"
test_environment: sandbox # (required) sandbox | production
test_period:
from: # (required) e.g. "2026-07-14"
to: # (required) e.g. "2026-07-21"
timezone: UTC # (required) timezone all timestamps are recorded in
use-case_description: # (required) API integration overview and target use cases
# ============================================================
# PREREQUISITES
# ============================================================
token_management:
credentials_stored_securely:
confirmed: false # (required) true | false
notes: >
# Describe where client_id and client_secret are stored.
# e.g. "Stored as environment variables via AWS Secrets Manager"
token_caching:
confirmed: false # (required) true | false
ttl_seconds: # (required) token TTL used e.g. 3600
buffer_seconds: # (required) refresh buffer e.g. 600 (10 mins)
notes: >
# Describe your in-memory caching strategy
concurrent_refresh_protection:
confirmed: false # (required) true | false
mechanism: # e.g. "mutex", "distributed lock", "single-threaded queue"
idempotency:
keys_on_all_post_requests:
confirmed: false # (required) true | false
derivation_strategy: >
# (required) Describe how keys are generated.
# e.g. "deterministicUuid('lorum-payment:' + payment.referenceNo)"
duplicate_request_test:
confirmed: false # (required) true | false
example:
request:
method: POST
path: # e.g. "/v1/accounts/***\/payments"
idempotency_key: "***"
body: {}
first_response:
status: # e.g. 201
second_response:
status: # e.g. 201 — must match first response
# ============================================================
# 1. CURRENCY ONBOARDING
# ============================================================
currency_onboarding:
document_upload:
- request:
method: POST
path: "/v1/customers/***/documents"
timestamp: # ISO 8601
body:
document_type: # e.g. "passport", "trade_license"
customer_id: "***"
response:
status: 201
body:
id: "***"
get_document:
request:
method: GET
path: "/v1/customers/***/documents/***"
timestamp:
response:
status: 200
body:
id: "***"
status: # e.g. "uploaded"
list_documents:
request:
method: GET
path: "/v1/customers/***/documents"
timestamp:
response:
status: 200
body:
results: [] # paste array of document objects
check_onboarding_status:
- scenario: not_onboarded
request:
method: GET
path: "/v1/customers/***/onboarding/***"
timestamp:
response:
status: 404
body:
error:
- scenario: onboarded
request:
method: GET
path: "/v1/customers/***/onboarding/***"
timestamp:
response:
status: 200
body:
status: # e.g. "completed"
webhooks:
onboarding_completed:
received: false # (required) true | false
event_id: "***"
timestamp:
customer_id: "***"
currency: # e.g. "AED"
http_response_returned: 200
onboarding_failed:
received: false # (required) true | false
event_id: "***"
timestamp:
customer_id: "***"
currency:
failure_reason:
code: # e.g. "missing_document"
msg:
http_response_returned: 200
ops_notification_confirmed: false # confirm ops team was notified
# ============================================================
# 2. MANAGING CUSTOMERS
# ============================================================
customers:
create_business:
request:
method: POST
path: "/v1/customers/businesses"
timestamp:
body:
display_name:
business_name:
registration_number: "***"
country_of_issuance: # e.g. "AE"
address:
line_1: "***"
line_2: "***"
country_code:
response:
status: 201
body:
id: "***"
create_individual:
request:
method: POST
path: "/v1/customers/individuals"
timestamp:
body:
display_name:
first_name:
last_name:
date_of_birth: "***"
nationality:
address:
line_1: "***"
line_2: "***"
country_code:
response:
status: 201
body:
id: "***"
get_customer:
request:
method: GET
path: "/v1/customers/***"
timestamp:
response:
status: 200
body:
id: "***"
status: # e.g. "approved"
update_customer:
request:
method: PATCH
path: "/v1/customers/***"
timestamp:
body: {} # fields being updated
response:
status: 200
list_customers:
request:
method: GET
path: "/v1/customers"
timestamp:
response:
status: 200
body:
pages:
next:
size:
results: []
add_shareholder:
request:
method: POST
path: "/v1/customers/***/shareholders"
timestamp:
body:
first_name:
last_name:
date_of_birth: "***"
nationality:
ownership_percentage:
response:
status: 201
body:
id: "***"
list_shareholders:
request:
method: GET
path: "/v1/customers/***/shareholders"
timestamp:
response:
status: 200
body:
results: []
get_shareholder:
request:
method: GET
path: "/v1/customers/***/shareholders/***"
timestamp:
response:
status: 200
body:
id: "***"
update_shareholder:
request:
method: PATCH
path: "/v1/customers/***/shareholders/***"
timestamp:
body: {}
response:
status: 200
# ============================================================
# 3. MANAGING VIRTUAL ACCOUNTS
# ============================================================
virtual_accounts:
create_account:
request:
method: POST
path: "/v1/customers/***/accounts"
timestamp:
idempotency_key: "***" # (required)
body:
account_name:
currency: # e.g. "AED"
account_type: virtual
external_id: "***"
response:
status: 201
body:
account_id: "***"
get_account:
request:
method: GET
path: "/v1/accounts/***"
timestamp:
response:
status: 200
body:
id: "***"
status: # e.g. "open"
currency:
current_balance:
available_balance:
update_account:
request:
method: PATCH
path: "/v1/accounts/***"
timestamp:
body: {}
response:
status: 200
list_customer_accounts:
request:
method: GET
path: "/v1/customers/***/accounts"
timestamp:
response:
status: 200
body:
results: []
list_all_accounts:
request:
method: GET
path: "/v1/accounts"
timestamp:
response:
status: 200
body:
pages:
next:
size:
results: []
get_balance_history:
request:
method: GET
path: "/v1/accounts/***/history"
timestamp:
response:
status: 200
body:
results: []
get_account_confirmation_letter:
request:
method: GET
path: "/v1/accounts/***/confirmation-letter"
timestamp:
response:
status: 200
webhooks:
account_opened:
received: false # (required)
event_id: "***"
timestamp:
account_id: "***"
currency:
http_response_returned: 200
account_updated:
received: false # (required)
event_id: "***"
timestamp:
account_id: "***"
http_response_returned: 200
# ============================================================
# 4. MAKING PAYMENTS
# ============================================================
payments:
estimate_fee:
request:
method: GET
path: "/v1/accounts/***/payments/estimate-fee/***"
timestamp:
response:
status: 200
body:
instructed_amount:
fee_amount:
amount_with_fee:
currency:
create_payment:
request:
method: POST
path: "/v1/accounts/***/payments"
timestamp:
idempotency_key: "***" # (required)
body:
amount:
currency:
beneficiary:
account_holder_name:
account_identifier: {}
type: # e.g. "business"
purpose: # e.g. "goods_bought"
external_reference: "***"
external_id: "***"
response:
status: 201
body:
transaction_id: "***"
get_transaction:
- scenario: settled
request:
method: GET
path: "/v1/transactions/***"
timestamp:
response:
status: 200
body:
id: "***"
state: Settled
direction: Outbound
currency:
instructed_amount:
- scenario: failed
request:
method: GET
path: "/v1/transactions/***"
timestamp:
response:
status: 200
body:
id: "***"
state: Failed
failure_reason:
bank_error:
get_transaction_confirmation_letter:
request:
method: GET
path: "/v1/transactions/***/confirmation-letter"
timestamp:
response:
status: 200
list_transactions:
request:
method: GET
path: "/v1/accounts/***/transactions"
timestamp:
response:
status: 200
body:
pages:
next: # null if single page, cursor value if paginated
size:
results: []
list_all_transactions:
request:
method: GET
path: "/v1/transactions"
timestamp:
response:
status: 200
body:
pages:
next:
size:
results: []
link_document_to_transaction:
request:
method: POST
path: "/v1/transactions/***/documents"
timestamp:
body:
document_id: "***"
response:
status: 201
get_linked_documents:
request:
method: GET
path: "/v1/transactions/***/documents"
timestamp:
response:
status: 200
body:
results: []
webhooks:
outbound_local_payment_created:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
instructed_amount:
fee_amount:
currency:
http_response_returned: 200
outbound_local_payment_submitted:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
http_response_returned: 200
outbound_local_payment_executed:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
http_response_returned: 200
outbound_local_payment_failed:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
failure_reason:
bank_error:
http_response_returned: 200
outbound_local_payment_returned:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
returned_transaction_id: "***"
returned_reason:
http_response_returned: 200
inbound_local_payment_settled:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
instructed_amount:
currency:
http_response_returned: 200
fee_debited:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
amount:
currency:
fee_target:
type: Transaction
reference: "***" # (required) must link to originating transaction
http_response_returned: 200
# ============================================================
# 5. MAKING INTERNAL TRANSFERS
# ============================================================
internal_transfers:
create_transfer:
request:
method: POST
path: "/v1/internal-transfers"
timestamp:
idempotency_key: "***" # (required)
body:
amount:
from_account_id: "***"
to_account_id: "***"
external_id: "***"
external_reference:
response:
status: 201
body:
transaction_id: "***"
get_transaction_outbound:
request:
method: GET
path: "/v1/transactions/***"
timestamp:
response:
status: 200
body:
id: "***"
type: internal_transfer
direction: Outbound
state: # e.g. Settled
instructed_amount:
get_transaction_inbound:
request:
method: GET
path: "/v1/transactions/***"
timestamp:
response:
status: 200
body:
id: "***"
type: internal_transfer
direction: Inbound
state:
instructed_amount:
list_transactions_source_account:
request:
method: GET
path: "/v1/accounts/***/transactions"
timestamp:
response:
status: 200
body:
results: []
list_transactions_destination_account:
request:
method: GET
path: "/v1/accounts/***/transactions"
timestamp:
response:
status: 200
body:
results: []
list_all_transactions:
request:
method: GET
path: "/v1/transactions"
timestamp:
response:
status: 200
body:
results: []
webhooks:
internal_transfer_created:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
to_account_id: "***"
amount:
http_response_returned: 200
internal_transfer_settled:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
http_response_returned: 200
inbound_internal_transfer_settled:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
from_account_id: "***"
amount:
http_response_returned: 200
internal_transfer_failed:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
failure_reason:
bank_error:
http_response_returned: 200
# ============================================================
# 6. MAKING FOREIGN EXCHANGE
# ============================================================
foreign_exchange:
get_quote:
request:
method: GET
path: "/v1/exchange/quote"
timestamp:
response:
status: 200
body:
rate:
currency:
counterpart_currency:
expires_at:
create_exchange:
request:
method: POST
path: "/v1/exchange"
timestamp:
idempotency_key: "***" # (required)
body:
from_account_id: "***"
to_account_id: "***"
amount:
currency:
counterpart_currency:
external_reference:
response:
status: 201
body:
transaction_id: "***"
get_transaction:
request:
method: GET
path: "/v1/transactions/***"
timestamp:
response:
status: 200
body:
id: "***"
type: exchange_transfer
state:
currency:
counterpart_currency:
rate:
instructed_amount:
list_transactions:
request:
method: GET
path: "/v1/accounts/***/transactions"
timestamp:
response:
status: 200
body:
results: []
webhooks:
outbound_exchange_transfer_created:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
counterpart_account_id: "***"
currency:
counterpart_currency:
amount:
counterpart_amount:
rate: # (required) decimal e.g. 3.6615
http_response_returned: 200
outbound_exchange_transfer_submitted:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
http_response_returned: 200
outbound_exchange_transfer_executed:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
http_response_returned: 200
inbound_exchange_transfer_settled:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
account_id: "***"
counterpart_account_id: "***"
currency:
amount:
rate:
http_response_returned: 200
outbound_exchange_transfer_failed:
received: false # (required)
event_id: "***"
timestamp:
transaction_id: "***"
failure_reason:
bank_error:
http_response_returned: 200
Updated 16 days ago
What’s Next
Get in touch with your Lorum representative to enable production access.