Kora keeps a whitelist of the public IPs allowed to send payout requests. Route your payout services through two fixed static EU IPs, enter the pair once under Settings > Security > IP Whitelisting, and every disburse call and status check arrives authorized.
# one variable pins egress for every payout call
HTTPS_PROXY=https://user:pass@eu-01.outboundgateway.com:8443
# Kora's Payout API, Bearer secret key, no edits
curl -s "$KORAPAY_BASE_URL/merchant/api/v1/transactions/disburse" \
-H "Authorization: Bearer $KORAPAY_SECRET_KEY" \
-d @payout.json
Kora's instructions are short: whitelist the public IP address of the server used for payout requests on the merchant dashboard. Living up to that sentence is the hard part, because payout traffic is a whole loop of calls and cloud egress rarely sits still.
Kora documents the whitelist for payout traffic specifically: log in to the merchant dashboard, open Settings > Security > IP Whitelisting, and enter the public IP of the server that sends payout requests. Money-moving calls are bound to named addresses, so the whole arrangement depends on those addresses staying true.
A disburse request comes back with status processing. The verdict lands later as a transfer.success or transfer.failed webhook, and the docs tell you to query the transaction for its final state. Retries and status checks are authenticated API calls too, and each one has to come from an address on the list.
Kora's error-handling guidance is blunt: do not treat 502, 503, 504, or 500 responses as failed payouts. Instead, verify through the payout verification API before giving value, and the docs note that losses from skipping that step sit with you. Every ambiguous error turns into another call that must leave from a listed address.
The dashboard field expects the public IP of your server, singular and stable. Autoscaled pods, rotating NAT gateways, and redeployed containers change that address without asking, and payout traffic leaving from an address that is not on the list stops being authorized mid-run.
Your Payout Services
disburse workers · bank lookups · retry jobs
HTTPS Proxy
(Static EU IP)
Kora
api.korapay.com/merchant/api/v1
On the Kora side, every request your payout stack makes arrives from the same two EU addresses, whether it left a scheduled worker, a container, or a retry loop. Both belong on the whitelist from the first day, so a failover between proxy nodes changes nothing the dashboard can see.
HTTPS_PROXY once in your environment and every Kora API family you use, disburse calls, bank and resolve lookups, payout history, card charges, leaves through the same pair.api.korapay.com. Your Bearer secret key stays encrypted past the proxy hop, and Kora's optional AES-256-GCM payload encryption keeps working unchanged.Kora's guidance assumes you can name the server that sends payout requests. Two fixed addresses answer that question once for every pod, worker, and cron job in the fleet, and the dashboard list stops chasing your infrastructure.
Disburse, listen for the webhook, query for the final state, and after any 5xx, verify again. That loop spans minutes or hours of authenticated calls, and the same pair carrying all of them keeps the whole loop inside the whitelist from first request to final confirmation.
African payouts rarely run on a single provider. Our Flutterwave static IP guide and Paystack guide show the same two addresses clearing other whitelists, so the pair you list for Kora keeps working wherever else your money moves.
Teams whose Kora payout traffic has to arrive from an address the dashboard knows, on every call.
Kora covers Nigeria, Ghana, Kenya, Tanzania, South Africa, Egypt, Côte d'Ivoire, and Cameroon. European platforms paying salaries, suppliers, and refunds across those corridors send every transfer from one fixed EU origin.
Kora supports bulk payouts over the same API. Off-hours payroll runs loop disburse and verification calls for dozens of beneficiaries while nobody is watching, which is exactly when an unlisted egress address hurts most.
The payout history endpoint at /merchant/api/v1/payouts takes your secret key and feeds finance reconciliation. Pollers and reporting jobs inherit the proxy pair automatically, with no second integration to wire.
Autoscaled pods, spot nodes, and rotating NAT gateways never own a stable address of their own. Point them at the pair and the whitelist stops caring what the orchestrator did overnight, as our Kubernetes guide shows.
One environment variable routes every Bearer-authenticated Kora payout call out of your fixed EU address.
Declare the variable in every place your payout code runs, from task definitions to cron wrappers. Kora authenticates each request with a Bearer secret key, and the proxy alters neither the path nor the header.
# .env, task definition, or Kubernetes secret, wherever env config lives
HTTPS_PROXY=https://user:pass@eu-01.outboundgateway.com:8443
KORAPAY_BASE_URL=https://api.korapay.com
KORAPAY_SECRET_KEY=sk_live_...
# request a payout, now egressing from your static EU IP
curl -s "$KORAPAY_BASE_URL/merchant/api/v1/transactions/disburse" \
-H "Authorization: Bearer $KORAPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"reference":"salary-run-042","destination":{"type":"bank_account","amount":25000.00,"currency":"NGN","bank_account":{"bank":"033","account":"0000000000"},"customer":{"email":"jane@example.com"}}}'
# reference must be unique and at least 5 characters; test keys
# differ from live keys, the address pair does not
Plain requests picks up HTTPS_PROXY from the environment on its own, no extra wiring.
import os
import requests
# requests reads HTTPS_PROXY from the environment automatically
payout = {
"reference": "salary-run-042",
"destination": {
"type": "bank_account",
"amount": 25000.00,
"currency": "NGN",
"bank_account": {"bank": "033", "account": "0000000000"},
"customer": {"email": "jane@example.com"},
},
}
resp = requests.post(
os.environ["KORAPAY_BASE_URL"] + "/merchant/api/v1/transactions/disburse",
json=payout,
headers={"Authorization": f"Bearer {os.environ['KORAPAY_SECRET_KEY']}"},
)
# expect status "processing"; the final word arrives as a
# transfer.success or transfer.failed webhook, then query the
# transaction to confirm before giving value
Node's built-in fetch ignores proxy environment variables, so hand it undici's ProxyAgent explicitly.
import { ProxyAgent } from 'undici';
const res = await fetch(`${process.env.KORAPAY_BASE_URL}/merchant/api/v1/transactions/disburse`, {
method: 'POST',
dispatcher: new ProxyAgent(process.env.HTTPS_PROXY),
headers: {
'Authorization': `Bearer ${process.env.KORAPAY_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payout),
});
// same path, same Bearer header, same fixed EU origin as curl;
// after any 502/503/504/500, verify the payout before treating
// it as failed, the error is not a verdict on the transfer
Both addresses, one entry each, and the payout list is settled for the life of the integration.
# listing the pair, once:
# log in to the Kora (merchant) dashboard
# Settings -> Security -> IP Whitelisting
# enter BOTH fixed IPv4 addresses from your account
# click OK
# webhook housekeeping, also on the dashboard:
# Settings -> API Configuration tab -> payout webhook URL,
# public and unauthenticated, exactly as the docs require
# on every delivery, verify the x-korapay-signature header:
# HMAC SHA256 of ONLY the data object, keyed with your
# secret key, then answer 200 or Kora retries for 72 hours
Both IPs on the list from day one
Every account gets its own pair of static EU IPv4 addresses. Enter both under IP Whitelisting once, and the payout loop Kora documents, disburse, webhook, query, and verify after errors, runs entirely from addresses the dashboard already admits. New workers inherit the pair automatically, and a failover between proxy nodes never reopens the settings page.
Which traffic is covered? Outbound, Bearer-authenticated HTTPS from your servers, containers, and cron jobs to Kora's API: disburse requests, bank and mobile money lookups, payout history, and card charges on api.korapay.com. Nothing inbound flows through the proxy. Kora POSTs webhook events such as transfer.success straight to the URL you configured in the API Configuration tab, and you authenticate each delivery by checking the x-korapay-signature header, an HMAC SHA256 of the data object signed with your secret key.
📖 Complete Documentation: For detailed examples, error handling, and advanced configurations, see the Python guide, the Node.js guide, or all guides.
Kora's own security page carries PCI DSS, ISO 27001, ISO 22301, and SOC 2 Type II among its certifications. The origin of your side of the connection is the part you control, and it is the easiest part to document.
Kora lists PCI DSS among its security certifications and stores card data under PCI-DSS requirements. On your side, a payout path that leaves from exactly two known EU addresses is the kind of defined network control an assessor can check off in one line.
The same page carries ISO 27001, ISO 22301, and SOC 2 Type II badges, which tells you how Kora runs its controls. Documenting yours gets simpler when payout egress is two EU addresses and a proxy config entry rather than a spreadsheet of rotating cloud IPs.
Both fixed IPs sit under IP Whitelisting, so disburse calls, retries, and verification queries all arrive from listed addresses no matter how many services and jobs originate them.
Type the two addresses into Settings > Security > IP Whitelisting once and click OK. Deploys, scale-outs, and migrations stop involving the merchant dashboard at all.
The two addresses back each other up, so a proxy node stepping out mid-payroll-run is invisible to Kora and to the disburse loop that is still in flight.
New pods, regions, and cron workers inherit the same pair from the environment. The whitelist you complete at launch is still the whole story at a hundred times the traffic.
Your sk_live_ secret key travels as a Bearer token inside TLS the proxy passes through as ciphertext. If you also use Kora's optional AES-256-GCM payload encryption, it keeps working byte for byte.
Starting from €19/month. Flexible plans for every scale. Cancel anytime.
A single environment variable points every Kora payout call at two fixed EU addresses the merchant dashboard already lists, from the first test disburse to the ten-thousandth production run.
Starting from €19/month. Flexible plans for every scale. Cancel anytime.
Yes. The merchant dashboard exposes it under Settings > Security > IP Whitelisting, and Kora's documentation describes it for the public IP addresses of the servers used for payout requests. Enter both fixed IPv4 addresses on your account once, click OK, and the payout services behind the proxy are covered from then on.
Set HTTPS_PROXY once in the environment where your payout code runs. curl and Python requests honor it automatically, while Node's built-in fetch needs an explicit undici ProxyAgent. The disburse path, the api.korapay.com host, and the Bearer secret-key header all stay exactly as Kora documents them.
They are inbound and never touch the proxy. Kora POSTs events like transfer.success to the webhook URL you set in the API Configuration tab, so your receiver needs no proxy configuration. Verify each delivery by recomputing the x-korapay-signature header, an HMAC SHA256 over the data object with your secret key, respond with a 200, and expect retries for up to 72 hours if you do not.
Tell us what your payout stack runs on, and we will map exactly where the pair plugs in.
Contact Our Founders →