🇪🇺 Payout-whitelist-ready egress for Kora

Static IP for Korapay

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.

payout-worker · shell

# 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

The payout list is only as good as the addresses on it

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.

The list guards the money-movers

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.

Processing is not the end of the story

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.

A 5xx is not a verdict

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.

Cloud egress refuses to hold still

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.

The Solution

Enter two fixed EU IPs once, then leave the list alone

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.

Declare 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.
Either address can carry the whole payout workload, so the failover path never sends anyone back to Settings > Security > IP Whitelisting to type a new entry.
TLS runs straight through to 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.

Why payment teams route Kora through OutboundGateway

A list that stays complete

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.

Matches the async payout rhythm

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.

One pair across every PSP

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.

Designed for

Teams whose Kora payout traffic has to arrive from an address the dashboard knows, on every call.

Platforms paying into African markets

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.

Bulk payout schedulers

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.

Reconciliation and history pollers

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.

Teams on ephemeral infrastructure

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.

Implementation

One environment variable routes every Bearer-authenticated Kora payout call out of your fixed EU address.

Shell / environment

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

Python (requests)

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.js (undici ProxyAgent)

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

Run the dashboard steps once

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.

African payment rails, a European origin

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.

PCI DSS, stated plainly

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.

ISO 27001 and SOC 2 Type II posture

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.

What you get

The payout list stays satisfied

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.

One entry, not a habit

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.

Failover without touching the list

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.

Scale changes nothing

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.

Secret keys stay sealed

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.

Simple pricing

Starting from €19/month. Flexible plans for every scale. Cancel anytime.

List the pair once, keep paying out

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.

Frequently asked questions

Does Kora support IP whitelisting?

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.

How do I route Kora API calls through the proxy?

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.

What about Kora's webhooks?

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.

Two addresses Kora's payout list already knows

Tell us what your payout stack runs on, and we will map exactly where the pair plugs in.

Contact Our Founders →