<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Afriex</title>
    <description>The latest articles on DEV Community by Afriex (afriex).</description>
    <link>https://dev.to/afriex</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F12790%2Fb22225a9-dfbf-4c5a-a15a-2d25dba8e294.jpeg</url>
      <title>DEV Community: Afriex</title>
      <link>https://dev.to/afriex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/afriex"/>
    <language>en</language>
    <item>
      <title>Build a Remittance App with the Afriex Cross-border API</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:54:02 +0000</pubDate>
      <link>https://dev.to/afriex/build-a-remittance-app-with-the-afriex-business-api-1adg</link>
      <guid>https://dev.to/afriex/build-a-remittance-app-with-the-afriex-business-api-1adg</guid>
      <description>&lt;p&gt;Sending money across borders involves more moving parts than most developers expect. You need to know the live exchange rate before the user confirms. You need to register the recipient correctly. You need to attach their bank account or mobile wallet. You need to trigger the transfer and then track it through to settlement. And you need to handle failures gracefully when something goes wrong in the corridor.&lt;/p&gt;

&lt;p&gt;Most of that complexity is what the Afriex Cross-border API is built to handle. This article walks through the full remittance flow — from registering a sender to confirming delivery, using the Afriex SDK from start to finish.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get your API key
&lt;/h2&gt;

&lt;p&gt;Everything starts at &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;business.afriex.com&lt;/a&gt;. Create a Business account if you do not have one. Once you are in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Developer&lt;/strong&gt; then &lt;strong&gt;API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create new keys&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvvnd8m3nazk5vsnevlh6.png" alt="Afriex business dashboard developer tab" width="800" height="323"&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name (e.g. "Remittance App")&lt;/li&gt;
&lt;li&gt;Set the permissions your use case needs — for a remittance app, you need at minimum: &lt;strong&gt;View wallet balances&lt;/strong&gt;, &lt;strong&gt;Initiate withdrawals&lt;/strong&gt;, &lt;strong&gt;View payment methods&lt;/strong&gt;, and &lt;strong&gt;Add or manage payment methods&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F26e6bba5sakudwvedub8.png" alt="Afriex business dashboard API permissions sheet" width="800" height="367"&gt;
&lt;/li&gt;
&lt;li&gt;Copy the key immediately. You cannot view it again after leaving the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Store it as an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;span class="nv"&gt;AFRIEX_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging  &lt;span class="c"&gt;# switch to production when you go live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start in the &lt;code&gt;staging&lt;/code&gt; environment so nothing real moves while you build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install and initialize the SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @afriex/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize once and reuse the instance across your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/afriex.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@afriex/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Afriex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_ENVIRONMENT&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryableStatusCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;webhookPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;retryConfig&lt;/code&gt; handles transient failures automatically. If the Afriex API returns a 429 or a 503, the SDK retries up to three times with a delay between attempts, so you do not have to write that logic yourself. The &lt;code&gt;webhookPublicKey&lt;/code&gt; is used later for signature verification, you can get it from the &lt;strong&gt;Developers &amp;gt; Webhooks&lt;/strong&gt; section of your dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Show the live exchange rate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F95ivqiv4kwaet3ltrwgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F95ivqiv4kwaet3ltrwgb.png" alt="showing Dollar to Naira exchange rate" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before anything else, show the sender what rate they are getting. A remittance user deciding between providers makes that decision based on the rate they see. Never show a hardcoded or cached rate — always fetch live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRates&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fromSymbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;toSymbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rate not available for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage: sender sends USD, recipient gets NGN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`1 USD = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; NGN`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call returns the live mid-market rate for the currency pair. Show it to the user with a clear note about when it was fetched, since rates can shift between the moment they see it and the moment they confirm. The rate the user sees is informational — the actual conversion happens on Afriex's end when the transaction is created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create the recipient as a customer
&lt;/h2&gt;

&lt;p&gt;The recipient needs to exist in the Afriex system before you can attach a payment method or send them money. This is a one-time step per recipient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerRecipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// E.164 format, e.g. +2348012345678&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ISO 3166-1 alpha-2, e.g. NG, KE, GH&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;registerRecipient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;victory@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+2348012345678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// store this — you need it in every subsequent call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the &lt;code&gt;customerId&lt;/code&gt; that comes back. Every subsequent API call references this ID rather than the recipient's personal details.&lt;/p&gt;

&lt;p&gt;If you are building a product where senders send to the same recipient repeatedly (for example, a diaspora parent sending to family), store the &lt;code&gt;customerId&lt;/code&gt; against the recipient record in your own database on first creation and skip this step for future transfers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Attach the recipient's payment method
&lt;/h2&gt;

&lt;p&gt;Now tell Afriex where to send the money — the recipient's bank account or mobile wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bank account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;attachBankAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// bank code, e.g. "058" for GTBank Nigeria&lt;/span&gt;
  &lt;span class="nl"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;paymentMethod&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bankMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;attachBankAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;058&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GTBank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bankMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// store this too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mobile money
&lt;/h3&gt;

&lt;p&gt;For recipients receiving on MTN, M-Pesa, Airtel, or similar networks, the channel changes but the pattern is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mobileMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+2348012345678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recipient's mobile number&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MTN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MTN Mobile Money&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure which institution code to use? Call &lt;code&gt;afriex.paymentMethods.listInstitutions({ countryCode: "NG", channel: "BANK_ACCOUNT" })&lt;/code&gt; to get the full list for any country and channel combination.&lt;/p&gt;

&lt;p&gt;Like the &lt;code&gt;customerId&lt;/code&gt;, save the &lt;code&gt;paymentMethodId&lt;/code&gt; that comes back. This is what you pass when you create the transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Send the transfer
&lt;/h2&gt;

&lt;p&gt;This is the call that actually moves money. Everything before this was set up, this is the execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendRemittance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// currency the sender is sending&lt;/span&gt;
  &lt;span class="nl"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// currency the recipient receives&lt;/span&gt;
  &lt;span class="nl"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// your internal reference for this transfer&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idem-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;narration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Remittance — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;merchantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transfer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendRemittance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bankMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;txn-478-001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// the Afriex transaction ID — store this&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// starts as PENDING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth understanding about this call.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;idempotencyKey&lt;/code&gt; is derived from your own reference. If this call fails due to a network error and your code retries it, the same key means Afriex will not create a duplicate transaction. It recognizes the key and returns the existing transaction instead of creating a new one.&lt;/p&gt;

&lt;p&gt;The transaction comes back with a status of &lt;code&gt;PENDING&lt;/code&gt;. That is not a failure — it is the expected starting state. The actual settlement happens asynchronously, and Afriex tells you what happened next through webhooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Track the transfer through webhooks
&lt;/h2&gt;

&lt;p&gt;You will not know the outcome of a transfer by polling the response from the &lt;code&gt;create&lt;/code&gt; call. Afriex sends a signed HTTP POST to your webhook URL every time the transaction status changes. This is how you know when money has actually landed.&lt;/p&gt;

&lt;p&gt;First, register your webhook URL in the Afriex dashboard under &lt;strong&gt;Developers &amp;gt; Webhooks&lt;/strong&gt;. Copy the webhook public key from that page and add it to your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-webhook-public-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use the built-in handler from the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Signature verification&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAndParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Webhook handler (Next.js route example)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Read raw body before any parsing — the signature was computed against these exact bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawBody&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Update your database with the new status&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateTransferStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Money has landed — notify the sender&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer was delivered.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IN_REVIEW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Compliance hold — not a failure, just needs time&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer is under review.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RETRY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// The network is retrying automatically — no action needed&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Terminal failure — let the sender know and offer a retry&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer could not be completed.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few important things about how this handler is written.&lt;/p&gt;

&lt;p&gt;It reads the raw body with &lt;code&gt;req.text()&lt;/code&gt; before parsing anything. The SDK handles the RSA-SHA256 signature verification which was computed against those exact &lt;code&gt;rawBody&lt;/code&gt; bytes. If you parse to JSON first and re-serialize, the verification will fail even if the payload is genuine.&lt;/p&gt;

&lt;p&gt;It returns &lt;code&gt;200&lt;/code&gt; immediately. Afriex expects a response within about five seconds. If your handler does slow work (database writes, email sending) before returning, put that work in a queue and return immediately. Afriex will retry delivery up to twelve times with exponential backoff if it does not get a success response.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IN_REVIEW&lt;/code&gt; and &lt;code&gt;RETRY&lt;/code&gt; are not failures. &lt;code&gt;IN_REVIEW&lt;/code&gt; means a compliance hold that will resolve into &lt;code&gt;SUCCESS&lt;/code&gt; or &lt;code&gt;REJECTED&lt;/code&gt;. &lt;code&gt;RETRY&lt;/code&gt; means the payment network is handling it automatically. Treating either as a failure will cause you to alert users unnecessarily.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full picture
&lt;/h2&gt;

&lt;p&gt;Here is the complete flow in sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Fetch live rate          →  afriex.rates.getRates()
2. Register recipient       →  afriex.customers.create()
3. Attach payment method    →  afriex.paymentMethods.create()
4. Send the transfer        →  afriex.transactions.create()
5. Receive status updates   →  afriex.webhooks.verifyAndParse()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steps 2 and 3 are one-time per recipient. For a sender who sends to the same person repeatedly, you store the &lt;code&gt;customerId&lt;/code&gt; and &lt;code&gt;paymentMethodId&lt;/code&gt; and jump straight to step 4 on subsequent transfers.&lt;/p&gt;

&lt;p&gt;Afriex handles the currency conversion, the corridor routing, and the settlement. Your application handles the user flow, the data storage, and the notification layer. The two concerns stay clean and separate.&lt;/p&gt;

&lt;p&gt;The full API reference is at &lt;a href="https://docs.afriex.com" rel="noopener noreferrer"&gt;docs.afriex.com&lt;/a&gt; and the Business dashboard is at &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;business.afriex.com&lt;/a&gt;. If you have questions, drop them in the comments or reach out on X &lt;a href="https://x.com/codewithveek" rel="noopener noreferrer"&gt;@codewithveek&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>fintech</category>
      <category>crossborder</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Accept USDT and USDC Payments and Settle Automatically in USD with Afriex</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Mon, 22 Jun 2026 11:16:12 +0000</pubDate>
      <link>https://dev.to/afriex/how-to-accept-usdt-and-usdc-payments-and-settle-automatically-in-usd-with-afriex-2m79</link>
      <guid>https://dev.to/afriex/how-to-accept-usdt-and-usdc-payments-and-settle-automatically-in-usd-with-afriex-2m79</guid>
      <description>&lt;p&gt;Stablecoin payments are becoming a mainstream way for businesses across Africa and global markets to collect funds. USDT and USDC now account for a significant share of cryptocurrency transaction volume because they offer fast settlement, lower transaction costs, and predictable value compared to traditional cross-border payment methods.&lt;/p&gt;

&lt;p&gt;For many businesses, however, accepting stablecoins introduces a new challenge: managing wallets, handling conversions, and moving funds into a usable currency.&lt;/p&gt;

&lt;p&gt;The Afriex crypto wallet endpoint simplifies this process. You generate a wallet address for USDT or USDC, your customer sends funds to that address, and Afriex automatically converts the deposit into its USD equivalent in your business balance.&lt;/p&gt;

&lt;p&gt;There is no manual conversion step, no separate swap operation, and no need to hold cryptocurrency after the payment is received.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generate a USDT or USDC wallet address
&lt;/h2&gt;

&lt;p&gt;Getting started requires a single API call and one required parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or "USDC"&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optional-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you omit &lt;code&gt;customerId&lt;/code&gt;, the wallet belongs directly to your business account. This is useful when you want a single shared collection address for all incoming crypto payments.&lt;/p&gt;

&lt;p&gt;If you provide a &lt;code&gt;customerId&lt;/code&gt;, the wallet is associated with that specific customer. This approach is ideal when you need automatic payment attribution and want to know exactly which customer made a deposit without requiring payment references or manual reconciliation.&lt;/p&gt;

&lt;p&gt;The endpoint is idempotent. Requesting a wallet for the same asset and customer multiple times returns the same wallet address each time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"695271a3ba52c13b669fad2b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x1234567890123456789012345678901234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETHEREUM_MAINNET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TYASr5UV6HEcXatwdFQfmLVUqQQQMUxHLS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRON_MAINNET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For USDT wallets, Afriex returns addresses on both Ethereum and Tron networks. You can share whichever network your customer prefers. In most cases, Tron offers lower transaction fees and is often the more economical option for senders.&lt;/p&gt;




&lt;h2&gt;
  
  
  How crypto deposits are converted to USD
&lt;/h2&gt;

&lt;p&gt;Once USDT or USDC arrives at the generated wallet address, Afriex automatically converts the funds into their USD equivalent and records the transaction as a &lt;code&gt;DEPOSIT&lt;/code&gt; in your account.&lt;/p&gt;

&lt;p&gt;No additional API calls are required.&lt;/p&gt;

&lt;p&gt;The balance that appears in your business wallet is already denominated in USD, making it easier to manage accounting, payouts, and reporting without handling cryptocurrency directly.&lt;/p&gt;

&lt;p&gt;This workflow is especially useful for businesses that want to accept stablecoin payments while operating entirely in USD.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detecting successful crypto payments
&lt;/h2&gt;

&lt;p&gt;One important operational detail is that there is currently no dedicated webhook event for crypto-to-USD conversion deposits.&lt;/p&gt;

&lt;p&gt;If your application relies on real-time payment notifications, you should monitor incoming deposits by polling the transactions endpoint and looking for new &lt;code&gt;DEPOSIT&lt;/code&gt; records associated with the wallet's payment method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// filter or sort by createdAt to catch new ones&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common production approach is to poll periodically, track the latest processed transaction, and reconcile new deposits as they appear.&lt;/p&gt;

&lt;p&gt;This ensures your system can reliably identify successful payments and update customer records automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production requirements and limitations
&lt;/h2&gt;

&lt;p&gt;Like Afriex virtual accounts and pool accounts, crypto wallet collection is available only in the production environment.&lt;/p&gt;

&lt;p&gt;The endpoint is not available in staging or sandbox environments.&lt;/p&gt;

&lt;p&gt;During development, you should test your payment processing logic using mocked responses and validate the end-to-end deposit flow after going live.&lt;/p&gt;

&lt;p&gt;If you provide a &lt;code&gt;customerId&lt;/code&gt;, the associated customer must be verified. Requests made with an unverified customer return a &lt;code&gt;400&lt;/code&gt; validation error instead of a wallet address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Crypto wallet vs virtual account
&lt;/h2&gt;

&lt;p&gt;Both crypto wallets and virtual accounts ultimately create attributable deposits in your Afriex account, but they solve different payment collection problems.&lt;/p&gt;

&lt;p&gt;Use a virtual account when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers pay via local bank transfer&lt;/li&gt;
&lt;li&gt;Customers prefer traditional banking rails&lt;/li&gt;
&lt;li&gt;You are collecting fiat currency payments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a crypto wallet when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers already hold USDT or USDC&lt;/li&gt;
&lt;li&gt;You want to accept stablecoin payments&lt;/li&gt;
&lt;li&gt;You serve freelancers, remote workers, diaspora customers, or crypto-native businesses&lt;/li&gt;
&lt;li&gt;Customers prefer blockchain-based payment methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Virtual accounts are covered in the virtual accounts guide.&lt;/p&gt;

&lt;p&gt;The Afriex crypto wallet endpoint is designed for businesses that want the speed and accessibility of stablecoin payments while keeping settlement and accounting simple through automatic USD conversion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Afriex support USDT on Tron?
&lt;/h3&gt;

&lt;p&gt;Yes. When you create a USDT wallet, Afriex returns wallet addresses for both the Ethereum and Tron networks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can provide either address to your customer depending on the network they intend to use. In many cases, Tron is preferred because transaction fees are typically lower than Ethereum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Afriex support USDC payments?
&lt;/h3&gt;

&lt;p&gt;Yes. The crypto wallet endpoint supports both USDT and USDC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once funds are received, Afriex automatically converts the deposit into its USD equivalent and credits your business balance.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are crypto deposits settled?
&lt;/h3&gt;

&lt;p&gt;Crypto deposits are settled automatically in USD.&lt;/p&gt;

&lt;p&gt;When a customer sends USDT or USDC to a generated wallet address:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The blockchain confirms the transaction.&lt;/li&gt;
&lt;li&gt;Afriex receives the stablecoin deposit.&lt;/li&gt;
&lt;li&gt;The deposit is automatically converted into USD.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;DEPOSIT&lt;/code&gt; transaction is created in your account.&lt;/li&gt;
&lt;li&gt;Your USD balance is updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No separate conversion request or manual swap operation is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I receive payment notifications through webhooks?
&lt;/h3&gt;

&lt;p&gt;Currently, Afriex does not emit a dedicated webhook event when a crypto deposit is converted into USD.&lt;/p&gt;

&lt;p&gt;To detect new crypto payments, periodically query the Transactions API and look for newly created &lt;code&gt;DEPOSIT&lt;/code&gt; transactions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many production systems poll this endpoint at regular intervals and reconcile newly discovered deposits automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I create separate wallets for different customers?
&lt;/h3&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Pass a &lt;code&gt;customerId&lt;/code&gt; when creating a wallet to generate a customer-scoped payment destination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes payment attribution significantly easier because deposits can be associated directly with the customer who owns the wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the crypto wallet endpoint available in sandbox mode?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;The crypto wallet endpoint is currently available only in production environments. During development, use mocked responses and test payment reconciliation workflows before validating against live deposits.&lt;/p&gt;

&lt;p&gt;Depending on how your customers prefer to pay, you may need a combination of crypto payment collection, bank transfers, and payout infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex Virtual Accounts
&lt;/h3&gt;

&lt;p&gt;Use virtual accounts when customers need to pay via local bank transfer rather than cryptocurrency.&lt;/p&gt;

&lt;p&gt;Virtual accounts provide dedicated account details that make payment collection and reconciliation easier for fiat transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local bank transfers&lt;/li&gt;
&lt;li&gt;Customer deposits&lt;/li&gt;
&lt;li&gt;Marketplace payments&lt;/li&gt;
&lt;li&gt;Subscription collections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Read next: &lt;a href="https://dev.to/afriex/afriex-virtual-accounts-a-developers-guide-to-dedicated-and-pool-accounts-260b"&gt;Afriex Virtual Accounts: A Developer's Guide to Dedicated and Pool Accounts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex Pool Accounts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.afriex.com/api-reference/endpoint/payment-methods/pool-account" rel="noopener noreferrer"&gt;Pool accounts&lt;/a&gt; are useful when you want to collect payments into a shared account and attribute funds programmatically after receipt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-volume payment collection&lt;/li&gt;
&lt;li&gt;Marketplaces&lt;/li&gt;
&lt;li&gt;Platforms with many users&lt;/li&gt;
&lt;li&gt;Automated reconciliation workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Transactions API
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.afriex.com/api-reference/endpoint/transactions/create" rel="noopener noreferrer"&gt;Transactions API&lt;/a&gt; is particularly important when working with crypto deposits because it allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect successful deposits&lt;/li&gt;
&lt;li&gt;Reconcile incoming payments&lt;/li&gt;
&lt;li&gt;Build internal payment ledgers&lt;/li&gt;
&lt;li&gt;Trigger business workflows after settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you plan to accept USDT or USDC payments in production, this endpoint will likely become a core part of your integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer Verification
&lt;/h3&gt;

&lt;p&gt;When creating customer-scoped wallets, ensure the associated customer has completed verification requirements.&lt;/p&gt;

&lt;p&gt;Unverified customers will receive validation errors when attempting to create crypto wallets tied to their account.&lt;/p&gt;

&lt;p&gt;Customer verification is therefore an important prerequisite for customer-specific payment collection workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Afriex crypto wallet endpoint makes it easy to accept USDT and USDC payments without managing conversions yourself. Generate a wallet address, receive stablecoin payments, and have deposits automatically settled in USD.&lt;/p&gt;

&lt;p&gt;If you're ready to start accepting stablecoin payments, &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;create an Afriex account&lt;/a&gt;, complete your business setup, and begin collecting USDT and USDC with automatic USD settlement.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>crypto</category>
      <category>crossborder</category>
      <category>payments</category>
    </item>
    <item>
      <title>I Built an Afriex MCP Prompt Cookbook So Developers Never Have to Stare at a Blank Prompt Again</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Sun, 21 Jun 2026 00:17:23 +0000</pubDate>
      <link>https://dev.to/afriex/i-built-an-afriex-mcp-prompt-cookbook-so-developers-never-have-to-stare-at-a-blank-prompt-again-1i1b</link>
      <guid>https://dev.to/afriex/i-built-an-afriex-mcp-prompt-cookbook-so-developers-never-have-to-stare-at-a-blank-prompt-again-1i1b</guid>
      <description>&lt;p&gt;A few weeks ago, I started exploring the Afriex MCP server.&lt;/p&gt;

&lt;p&gt;The setup was surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;Connect your MCP client.&lt;br&gt;
Configure your API key.&lt;br&gt;
Verify the connection.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;But then I ran into a different problem.&lt;/p&gt;

&lt;p&gt;Not a technical problem.&lt;/p&gt;

&lt;p&gt;A prompt problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Blank Prompt Problem
&lt;/h2&gt;

&lt;p&gt;Once everything was connected, I found myself staring at an empty prompt box.&lt;/p&gt;

&lt;p&gt;What should I ask?&lt;/p&gt;

&lt;p&gt;Sure, I could retrieve balances.&lt;/p&gt;

&lt;p&gt;I could create customers.&lt;/p&gt;

&lt;p&gt;I could generate virtual accounts.&lt;/p&gt;

&lt;p&gt;But what were the most useful workflows?&lt;/p&gt;

&lt;p&gt;What were the prompts that would actually help developers build real products?&lt;/p&gt;

&lt;p&gt;This isn't a problem unique to Afriex.&lt;/p&gt;

&lt;p&gt;It's becoming a common challenge across the entire MCP ecosystem.&lt;/p&gt;

&lt;p&gt;The infrastructure exists.&lt;/p&gt;

&lt;p&gt;The tools work.&lt;/p&gt;

&lt;p&gt;But many developers don't know where to start.&lt;/p&gt;


&lt;h2&gt;
  
  
  MCP Changes How We Build
&lt;/h2&gt;

&lt;p&gt;Traditionally, integrating a payment API looked something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read documentation&lt;/li&gt;
&lt;li&gt;Find the endpoint&lt;/li&gt;
&lt;li&gt;Write HTTP requests&lt;/li&gt;
&lt;li&gt;Parse responses&lt;/li&gt;
&lt;li&gt;Build business logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With MCP, the workflow looks very different.&lt;/p&gt;

&lt;p&gt;You can simply tell your AI assistant what you want to build.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a customer onboarding flow that:

- Collects customer details
- Generates a virtual account
- Displays payment instructions

Build it using Next.js and TypeScript.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually stitching everything together, the AI can interact with infrastructure through the MCP server.&lt;/p&gt;

&lt;p&gt;That's incredibly powerful.&lt;/p&gt;

&lt;p&gt;But only if you know what to ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;That's what led me to build the:&lt;/p&gt;

&lt;h2&gt;
  
  
  Afriex MCP Prompt Cookbook
&lt;/h2&gt;

&lt;p&gt;A collection of practical, production-oriented prompts designed specifically for developers building with Afriex MCP.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Copy.&lt;/p&gt;

&lt;p&gt;Paste.&lt;/p&gt;

&lt;p&gt;Build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of starting from scratch every time.&lt;/p&gt;

&lt;p&gt;The cookbook is open source and available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SonOfUri/afriex-mcp-cookbook" rel="noopener noreferrer"&gt;https://github.com/SonOfUri/afriex-mcp-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to explore the prompts, use them in your own projects, and contribute new recipes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Inside
&lt;/h2&gt;

&lt;p&gt;The cookbook is organized around real-world use cases.&lt;/p&gt;

&lt;p&gt;Not API endpoints.&lt;/p&gt;

&lt;p&gt;Not documentation pages.&lt;/p&gt;

&lt;p&gt;Actual products and workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;For developers who have just connected their MCP client.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieving balances&lt;/li&gt;
&lt;li&gt;Creating customers&lt;/li&gt;
&lt;li&gt;Monitoring transactions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Virtual Accounts
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer onboarding flows&lt;/li&gt;
&lt;li&gt;Collections systems&lt;/li&gt;
&lt;li&gt;Payment request workflows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Payments
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment links&lt;/li&gt;
&lt;li&gt;Payout systems&lt;/li&gt;
&lt;li&gt;Payment status tracking&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stablecoins
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USDC deposit flows&lt;/li&gt;
&lt;li&gt;Treasury monitoring&lt;/li&gt;
&lt;li&gt;Settlement operations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  AI Agents
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operations agents&lt;/li&gt;
&lt;li&gt;Treasury agents&lt;/li&gt;
&lt;li&gt;Monitoring agents&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  OpenClaw Integrations
&lt;/h2&gt;

&lt;p&gt;Examples focused on combining AI agents with payment infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Favorite Prompt
&lt;/h2&gt;

&lt;p&gt;One of my favorite recipes asks Cursor to build an entire virtual account collection flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using Afriex MCP:

Create a customer onboarding flow that:

- Collects customer details
- Creates a customer record
- Generates a virtual account
- Displays payment instructions

Build this as a modern Next.js application using TypeScript and TailwindCSS.

Show me the complete implementation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of workflow that would traditionally require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading documentation&lt;/li&gt;
&lt;li&gt;understanding endpoints&lt;/li&gt;
&lt;li&gt;designing data models&lt;/li&gt;
&lt;li&gt;wiring API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it can start with a single prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Think Prompt Libraries Matter
&lt;/h2&gt;

&lt;p&gt;We're entering a new phase of software development.&lt;/p&gt;

&lt;p&gt;Documentation remains important.&lt;/p&gt;

&lt;p&gt;SDKs remain important.&lt;/p&gt;

&lt;p&gt;APIs remain important.&lt;/p&gt;

&lt;p&gt;But prompts are becoming a new layer of developer experience.&lt;/p&gt;

&lt;p&gt;The best developer platforms won't just provide endpoints.&lt;/p&gt;

&lt;p&gt;They'll provide examples, recipes, workflows, and patterns that help developers move from idea to implementation faster.&lt;/p&gt;

&lt;p&gt;That's exactly what this cookbook is trying to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;The current version focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;virtual accounts&lt;/li&gt;
&lt;li&gt;stablecoins&lt;/li&gt;
&lt;li&gt;AI agents&lt;/li&gt;
&lt;li&gt;OpenClaw integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future additions will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more production workflows&lt;/li&gt;
&lt;li&gt;community-contributed recipes&lt;/li&gt;
&lt;li&gt;example applications&lt;/li&gt;
&lt;li&gt;advanced MCP patterns&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;One of the most surprising lessons I've learned while working with MCP is that access to tools isn't enough.&lt;/p&gt;

&lt;p&gt;Developers also need examples of what's possible.&lt;/p&gt;

&lt;p&gt;That's what the Afriex MCP Prompt Cookbook aims to provide.&lt;/p&gt;

&lt;p&gt;Not more documentation.&lt;/p&gt;

&lt;p&gt;Not another SDK.&lt;/p&gt;

&lt;p&gt;A collection of practical prompts that help developers build faster.&lt;/p&gt;

&lt;p&gt;Because sometimes the hardest part isn't connecting the infrastructure.&lt;/p&gt;

&lt;p&gt;It's knowing what to ask next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore The Cookbook
&lt;/h2&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SonOfUri/afriex-mcp-cookbook" rel="noopener noreferrer"&gt;https://github.com/SonOfUri/afriex-mcp-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building with Afriex MCP, I'd love to see what you create and what prompts you find most useful.&lt;/p&gt;

&lt;p&gt;Contributions, suggestions, and new recipes are always welcome.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>afriex</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Afriex Virtual Accounts: A Developer's Guide to Dedicated and Pool Accounts</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Sat, 20 Jun 2026 09:56:25 +0000</pubDate>
      <link>https://dev.to/afriex/afriex-virtual-accounts-a-developers-guide-to-dedicated-and-pool-accounts-260b</link>
      <guid>https://dev.to/afriex/afriex-virtual-accounts-a-developers-guide-to-dedicated-and-pool-accounts-260b</guid>
      <description>&lt;p&gt;Reconciling inbound payments seems straightforward until you have more than a handful of customers. Someone sends money to your business account, and now you need to determine who sent it, why they sent it, and what the payment was for. In most cases, this means asking customers to include a reference, which they will eventually forget.&lt;/p&gt;

&lt;p&gt;Virtual accounts solve this problem by reversing the model. Instead of having everyone pay into a single account, you generate a unique account number for a customer, a purpose, or even a single transaction. When funds arrive, you already know exactly who the payment belongs to. There is no reference matching, no guesswork, and no manual reconciliation.&lt;/p&gt;

&lt;p&gt;This guide explains how virtual accounts work in the Afriex Business API, including the two available account types, when to use each one, the key constraints you should understand before building, and the complete integration pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ways to collect: Dedicated accounts vs. pool accounts
&lt;/h2&gt;

&lt;p&gt;Afriex provides two distinct models for collecting inbound payments, and choosing the right one can significantly simplify your integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dedicated virtual accounts&lt;/strong&gt; (&lt;code&gt;VIRTUAL_BANK_ACCOUNT&lt;/code&gt;) are created specifically for an individual customer. You generate them through the API, and Afriex assigns a real account number linked directly to that customer in your system. Every deposit into that account is automatically attributed to the correct customer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pool accounts&lt;/strong&gt; (&lt;code&gt;POOL_ACCOUNT&lt;/code&gt;) take a different approach. Afriex maintains a shared account that multiple customers can pay into. Deposits are reconciled using a &lt;code&gt;reference&lt;/code&gt; value that you store and track on your side. There is no account creation step. You simply call the endpoint, receive the account details and reference, and provide them to your customer.&lt;/p&gt;

&lt;p&gt;A simple rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a &lt;strong&gt;dedicated virtual account&lt;/strong&gt; when you want a stable account number permanently assigned to a customer.&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;pool account&lt;/strong&gt; when you need a lightweight collection flow and are comfortable managing payment references yourself.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Important limitation: Production only
&lt;/h2&gt;

&lt;p&gt;Before implementing virtual accounts, be aware of one critical limitation: virtual accounts and pool accounts are only available in production environments.&lt;/p&gt;

&lt;p&gt;They are not supported in staging or sandbox environments. As a result, you cannot fully test the end-to-end collection flow before going live. The recommended approach is to build and unit test your integration against mocked responses, then carefully validate the complete payment flow after deployment to production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dedicated virtual accounts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Static vs. dynamic accounts
&lt;/h3&gt;

&lt;p&gt;Dedicated virtual accounts come in two variants. The API determines which type to create based on the parameters you provide alongside &lt;code&gt;currency&lt;/code&gt; and &lt;code&gt;customerId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static accounts&lt;/strong&gt; are created with a &lt;code&gt;label&lt;/code&gt; and never expire. The account number remains valid indefinitely, making them ideal for customers who need to fund an account repeatedly over time.&lt;/p&gt;

&lt;p&gt;Supported labels include:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SALES&lt;/code&gt;, &lt;code&gt;OPERATIONS&lt;/code&gt;, &lt;code&gt;PAYROLL&lt;/code&gt;, &lt;code&gt;COLLECTIONS&lt;/code&gt;, &lt;code&gt;VENDOR_PAYMENTS&lt;/code&gt;, &lt;code&gt;TAX&lt;/code&gt;, &lt;code&gt;REFUNDS&lt;/code&gt;, &lt;code&gt;MARKETING&lt;/code&gt;, &lt;code&gt;TREASURY&lt;/code&gt;, and &lt;code&gt;GENERAL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The label is purely organizational. It allows you to categorize accounts internally according to their intended use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic accounts&lt;/strong&gt; are created using an &lt;code&gt;amount&lt;/code&gt; instead of a &lt;code&gt;label&lt;/code&gt;. They expire after a short period and are designed for collecting a specific amount within a limited timeframe, such as a one-time invoice or order payment.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;amount&lt;/code&gt; fields are mutually exclusive. Including both in the same request will result in an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Static virtual account, grouped under SALES&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;staticAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;68e6717848e1f632e9686460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SALES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Dynamic virtual account, tied to a specific amount&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamicAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;68e6717848e1f632e9686460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes the account details you can share directly with the customer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paymentMethodId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"690cc5bbe2a1143ff6070119"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIRTUAL_BANK_ACCOUNT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"68e6717848e1f632e9686460"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"institution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FIDELITY BANK"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lily New"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3820404958"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"countryCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NG"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NGN virtual accounts and BVN requirements
&lt;/h3&gt;

&lt;p&gt;When creating a &lt;strong&gt;static NGN virtual account&lt;/strong&gt;, the customer must have a Bank Verification Number (BVN) on file. This is a regulatory requirement for permanently assigned Nigerian bank accounts.&lt;/p&gt;

&lt;p&gt;If the customer does not have a BVN recorded, account creation will fail.&lt;/p&gt;

&lt;p&gt;You can provide the BVN when creating the customer or update it later through the Customer KYC endpoint.&lt;/p&gt;

&lt;p&gt;This requirement applies only to static NGN virtual accounts. Dynamic virtual accounts are exempt, and the requirement does not apply to other currencies.&lt;/p&gt;

&lt;p&gt;If your product relies on static NGN accounts, collecting BVNs during onboarding will prevent failed account creation requests later in the user journey.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-customer account limits
&lt;/h3&gt;

&lt;p&gt;Virtual account creation is subject to limits based on the combination of business, customer, and currency.&lt;/p&gt;

&lt;p&gt;If you exceed the allowed number of accounts for a specific customer and currency, the API returns a &lt;code&gt;400&lt;/code&gt; response with the error code &lt;code&gt;VIRTUAL_ACCOUNT_LIMIT_REACHED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To avoid unnecessary creation attempts, consider checking for existing accounts before creating a new one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listVirtualAccounts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;68e6717848e1f632e9686460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// safe to create&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;68e6717848e1f632e9686460&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SALES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A breaking change to be aware of
&lt;/h3&gt;

&lt;p&gt;Previous versions of the API automatically created a virtual account when the list endpoint was called and no account existed.&lt;/p&gt;

&lt;p&gt;That behavior has been removed.&lt;/p&gt;

&lt;p&gt;The list endpoint is now strictly read-only. If no accounts exist, it returns a &lt;code&gt;200&lt;/code&gt; response with an empty array and does not create anything.&lt;/p&gt;

&lt;p&gt;Account creation must now be performed explicitly through the &lt;code&gt;POST&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;If your integration previously relied on a "get-or-create" workflow, update it to follow a clear check-then-create pattern like the example above.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pool accounts
&lt;/h2&gt;

&lt;p&gt;Pool accounts operate differently because customers are not assigned dedicated account numbers.&lt;/p&gt;

&lt;p&gt;Instead, you call the endpoint with a country code, and Afriex returns an existing pool account along with a unique &lt;code&gt;reference&lt;/code&gt; value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPoolAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;6929843e2c4653277440acc0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paymentMethodId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"69c2804b30314b491e48b305"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIRTUAL_BANK_ACCOUNT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6929843e2c4653277440acc0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6929843e2c4653277440acc0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"institution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UBA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12345678"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"countryCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NG"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several important differences compared to dedicated virtual accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no &lt;code&gt;currency&lt;/code&gt; parameter. Currency is inferred from the country code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NG&lt;/code&gt; automatically resolves to NGN.&lt;/li&gt;
&lt;li&gt;Pool accounts are only available in countries where Afriex has configured them.&lt;/li&gt;
&lt;li&gt;Requests for unsupported countries will return an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important field in the response is &lt;code&gt;reference&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a &lt;code&gt;customerId&lt;/code&gt; is provided, the returned reference matches that customer. Store this value in your database before sharing account details with the customer.&lt;/p&gt;

&lt;p&gt;When a deposit arrives, Afriex uses the reference to identify the payment, allowing your system to correctly attribute funds to the appropriate customer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Dedicated Virtual Account&lt;/th&gt;
&lt;th&gt;Pool Account&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account number&lt;/td&gt;
&lt;td&gt;Unique per customer&lt;/td&gt;
&lt;td&gt;Shared across multiple customers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Creation required&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Recurring or long-term customer payments&lt;/td&gt;
&lt;td&gt;Quick collections and one-off payments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer identification&lt;/td&gt;
&lt;td&gt;Automatic via account number&lt;/td&gt;
&lt;td&gt;Via stored reference value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supports repeated top-ups&lt;/td&gt;
&lt;td&gt;Yes (static accounts)&lt;/td&gt;
&lt;td&gt;Yes, but requires reference tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expires&lt;/td&gt;
&lt;td&gt;Static: No, Dynamic: Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BVN required for NGN&lt;/td&gt;
&lt;td&gt;Yes (static NGN accounts only)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-customer account limits&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No customer-level creation limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconciliation complexity&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ideal use cases&lt;/td&gt;
&lt;td&gt;Wallets, subscriptions, marketplaces, recurring billing&lt;/td&gt;
&lt;td&gt;Checkout payments, invoices, temporary collections&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Supported currencies
&lt;/h2&gt;

&lt;p&gt;Virtual accounts are currently available across four primary currencies: USD, NGN, GBP, and EUR.&lt;/p&gt;

&lt;p&gt;Current country coverage is shown below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Virtual Account Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nigeria&lt;/td&gt;
&lt;td&gt;NGN&lt;/td&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kenya&lt;/td&gt;
&lt;td&gt;KES&lt;/td&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;United States&lt;/td&gt;
&lt;td&gt;USD&lt;/td&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghana&lt;/td&gt;
&lt;td&gt;GHS&lt;/td&gt;
&lt;td&gt;Coming soon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;United Kingdom, all EU countries&lt;/td&gt;
&lt;td&gt;GBP / EUR&lt;/td&gt;
&lt;td&gt;Coming soon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If virtual accounts are not yet available in your target market, plan for an alternative collection strategy. Depending on availability, that may mean using pool accounts or another payment method until dedicated virtual accounts become available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconciliation pattern
&lt;/h2&gt;

&lt;p&gt;The typical lifecycle of a virtual account payment looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the customer in your system and register them with Afriex if necessary.&lt;/li&gt;
&lt;li&gt;Create a static or dynamic virtual account for that customer.&lt;/li&gt;
&lt;li&gt;Store the &lt;code&gt;paymentMethodId&lt;/code&gt; and &lt;code&gt;accountNumber&lt;/code&gt; in your database.&lt;/li&gt;
&lt;li&gt;Display the account details to the customer through your checkout flow, invoice, or payment instructions.&lt;/li&gt;
&lt;li&gt;The customer transfers funds to the virtual account.&lt;/li&gt;
&lt;li&gt;Afriex sends a deposit-related webhook event when funds arrive.&lt;/li&gt;
&lt;li&gt;Your webhook handler matches the event to the stored &lt;code&gt;paymentMethodId&lt;/code&gt; and updates the corresponding records.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This follows the same reconciliation philosophy described in the &lt;a href="https://dev.to/afriex/afriex-webhook-integration-guide-signature-verification-event-handling-and-production-best-3c9o"&gt;webhook integration guide&lt;/a&gt;. Webhooks should serve as the source of truth for payment events rather than relying on assumptions made by the application.&lt;/p&gt;

&lt;p&gt;For pool accounts, the process is similar, but the &lt;code&gt;reference&lt;/code&gt; becomes the primary identifier. Since multiple customers share the same account number, your webhook handler must use the stored reference to determine which customer a deposit belongs to.&lt;/p&gt;

&lt;p&gt;Without persisting that reference beforehand, accurate reconciliation becomes impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use each option?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use a dedicated virtual account when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have a known customer who will make recurring payments.&lt;/li&gt;
&lt;li&gt;You want deposits to be automatically attributed to that customer.&lt;/li&gt;
&lt;li&gt;You want to eliminate reference-based reconciliation entirely.&lt;/li&gt;
&lt;li&gt;You are building subscription products, marketplace wallets, stored balances, or recurring billing systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the &lt;strong&gt;dynamic&lt;/strong&gt; variant when collecting a specific amount within a limited time window, such as a single invoice or one-off purchase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a pool account when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to start collecting payments quickly.&lt;/li&gt;
&lt;li&gt;You do not want to manage virtual account creation.&lt;/li&gt;
&lt;li&gt;You want to avoid customer-level account limits and BVN requirements.&lt;/li&gt;
&lt;li&gt;You are comfortable maintaining reference mappings in your own system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A checkout experience that generates a unique reference for each order while reusing the same account details for all customers is a common use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use an alternative payment method when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The country or currency you need is not yet supported.&lt;/li&gt;
&lt;li&gt;Virtual account coverage is still marked as "coming soon."&lt;/li&gt;
&lt;li&gt;Your target corridor requires a collection method that Afriex does not currently support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always verify availability before implementation using the &lt;a href="https://docs.afriex.com/guides/supported-currencies" rel="noopener noreferrer"&gt;supported currencies reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you'd like to see a practical implementation, the &lt;a href="https://dev.to/afriex/send-a-link-get-paid-building-payment-links-with-afriex-13ak"&gt;Afriex payment links guide&lt;/a&gt; demonstrates how to build shareable payment links backed by dynamic virtual accounts. For complete endpoint documentation and request schemas, refer to the &lt;a href="https://docs.afriex.com/api-reference/endpoint/payment-methods/virtual-account" rel="noopener noreferrer"&gt;Afriex API reference&lt;/a&gt; for virtual accounts and pool accounts.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>fintech</category>
      <category>crossborder</category>
      <category>virtual</category>
    </item>
    <item>
      <title>What Afriex's Visa Direct Partnership Means for Users and Developers</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:42:51 +0000</pubDate>
      <link>https://dev.to/afriex/what-afriexs-visa-direct-partnership-means-for-users-and-developers-bl2</link>
      <guid>https://dev.to/afriex/what-afriexs-visa-direct-partnership-means-for-users-and-developers-bl2</guid>
      <description>&lt;p&gt;On November 1, 2025, Afriex announced a partnership with Visa to enable real-time cross-border payments across more than 160 markets. Through an integration with Visa Direct, Afriex users can now send international money transfers through Visa's global payment network, potentially reducing delivery times from days to near real time.&lt;/p&gt;

&lt;p&gt;The integration, which is available immediately through the Afriex mobile app, represents one of the company's most significant upgrades to its cross-border payments infrastructure.&lt;/p&gt;

&lt;p&gt;But what does the Afriex Visa Direct partnership actually mean for users, businesses, and developers? More importantly, what has changed, and what remains unclear?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Visa Direct?
&lt;/h2&gt;

&lt;p&gt;Visa Direct is Visa's real-time money movement platform that enables funds to be pushed directly to eligible bank accounts, cards, and wallets connected to the Visa network.&lt;/p&gt;

&lt;p&gt;Unlike traditional correspondent banking systems, where international money transfers can pass through multiple intermediary banks before settlement, Visa Direct uses Visa's existing payment network and partner banking infrastructure to move funds more efficiently.&lt;/p&gt;

&lt;p&gt;For account and wallet payouts, Visa Direct leverages Visa's network alongside local ACH and real-time payment systems to deliver money into the recipient's domestic banking ecosystem. The result is faster settlement, improved transfer visibility, and a more streamlined payment experience.&lt;/p&gt;

&lt;p&gt;Visa Direct itself is not new. The service has been used by payment companies around the world for years. What is new is Afriex's integration with Visa Direct and its decision to incorporate the service into its growing remittance and cross-border payments platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Afriex's Visa Direct Integration Changes International Transfers
&lt;/h2&gt;

&lt;p&gt;Before the Visa Direct integration, most Afriex transfers relied on traditional banking rails that could take anywhere from one to several business days to settle, depending on the payment corridor and recipient bank.&lt;/p&gt;

&lt;p&gt;According to Afriex CEO Tope Alabi, speed and transparency remain critical for families and businesses that depend on remittance payments. By connecting to Visa Direct, Afriex can now route eligible transfers through a payment network designed for near real-time money movement.&lt;/p&gt;

&lt;p&gt;For users, the experience is straightforward. Funds can be sent through the Afriex mobile app and delivered to supported Visa-connected destinations much faster than traditional settlement methods allow.&lt;/p&gt;

&lt;p&gt;The scale of the opportunity is substantial. Visa Direct reaches more than 160 markets and connects to billions of accounts worldwide. However, that number reflects the size of Visa's global payment infrastructure rather than 160 new markets independently added by Afriex.&lt;/p&gt;

&lt;p&gt;In practical terms, the Afriex Visa Direct integration gives users access to a much larger network of potential payout destinations without requiring Afriex to build entirely new payment rails for every market.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Important Caveat
&lt;/h2&gt;

&lt;p&gt;The headline numbers do not mean every transfer automatically qualifies for Visa Direct.&lt;/p&gt;

&lt;p&gt;Availability depends on whether the recipient's financial institution participates in the relevant Visa Direct program and whether the destination account is eligible to receive Visa Direct payouts.&lt;/p&gt;

&lt;p&gt;If the recipient bank does not support the service, Afriex will continue using its existing payout methods, including bank account transfers, mobile money, SWIFT transfers, and other supported payment channels.&lt;/p&gt;

&lt;p&gt;There is another limitation worth noting. According to the partnership announcement, Afriex currently accesses Visa Direct through its financial institution partner, but Push-to-Wallet functionality is not yet commercially available. The live service today primarily supports push-to-account and push-to-card transfers rather than the full suite of Visa Direct products.&lt;/p&gt;

&lt;p&gt;This distinction matters because the integration should be viewed as an additional fast payment rail, not a complete replacement for Afriex's existing payment infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Afriex Is Expanding Its Cross-Border Payment Infrastructure
&lt;/h2&gt;

&lt;p&gt;The Visa Direct partnership fits neatly into Afriex's broader strategy of expanding global payment access while improving the efficiency of its cross-border payments network.&lt;/p&gt;

&lt;p&gt;In recent years, Afriex has expanded its reach across Europe, enabling senders in countries such as the Netherlands, Romania, Germany, Ireland, Italy, France, Belgium, and Spain to send money to African markets. The company has also invested in corridor infrastructure connecting African markets with countries including China, India, and Pakistan.&lt;/p&gt;

&lt;p&gt;The strategy is clear.&lt;/p&gt;

&lt;p&gt;Rather than building new payment infrastructure for every international corridor, Afriex can leverage established global networks such as Visa Direct where they provide meaningful advantages. At the same time, the company can continue investing in proprietary infrastructure where existing financial systems remain slow, expensive, or underserved.&lt;/p&gt;

&lt;p&gt;The move also aligns with broader industry trends. As global remittance flows continue to grow, users increasingly expect faster transfers, better visibility into payment status, and more reliable settlement times.&lt;/p&gt;

&lt;p&gt;For Afriex, integrating Visa Direct is a logical step toward meeting those expectations while strengthening its position in the international money transfer market.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Afriex Visa Direct Partnership Means for Developers
&lt;/h2&gt;

&lt;p&gt;For developers, the picture is less exciting, at least for now.&lt;/p&gt;

&lt;p&gt;As of this writing, there is no public indication that Visa Direct functionality is available through the Afriex Business API or SDK. Public announcements surrounding the Afriex Visa Direct partnership focus exclusively on the consumer experience within the Afriex mobile app.&lt;/p&gt;

&lt;p&gt;Developers using the Afriex Business API should continue working with the payment methods currently documented by Afriex, including bank accounts, mobile money, SWIFT transfers, and other supported disbursement channels.&lt;/p&gt;

&lt;p&gt;If Visa Direct capabilities are eventually exposed through the API, they would likely appear as an additional payment method alongside existing options such as &lt;code&gt;BANK_ACCOUNT&lt;/code&gt;, &lt;code&gt;MOBILE_MONEY&lt;/code&gt;, and &lt;code&gt;SWIFT&lt;/code&gt;. That expectation is based on Afriex's current API structure, but it remains speculation rather than documented functionality.&lt;/p&gt;

&lt;p&gt;Until official documentation confirms otherwise, developers should avoid designing workflows that depend on Visa Direct access through the Afriex Business API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;The Afriex Visa Direct partnership represents a meaningful upgrade to the company's cross-border payments infrastructure. By connecting to Visa's global payment network, Afriex can offer faster international money transfers and improved payout speed across a large number of supported markets.&lt;/p&gt;

&lt;p&gt;For users, the benefits are immediate. Eligible transfers can move through Visa Direct's infrastructure and arrive significantly faster than they would through traditional banking channels.&lt;/p&gt;

&lt;p&gt;For developers, however, the story remains largely unchanged. Based on publicly available information, Visa Direct is currently a consumer-facing enhancement rather than a new API capability.&lt;/p&gt;

&lt;p&gt;The partnership is significant, but its biggest impact today is on transfer speed and user experience. Whether it eventually becomes part of the Afriex developer ecosystem remains a question for future product announcements.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>crossborder</category>
      <category>visa</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Building Global Payment Products Without Owning Banking Infrastructure</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Fri, 12 Jun 2026 23:46:55 +0000</pubDate>
      <link>https://dev.to/afriex/building-global-payment-products-without-owning-banking-infrastructure-2h76</link>
      <guid>https://dev.to/afriex/building-global-payment-products-without-owning-banking-infrastructure-2h76</guid>
      <description>&lt;p&gt;A decade ago, building a financial product required enormous resources.&lt;/p&gt;

&lt;p&gt;If you wanted to launch a payments company, you needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;banking relationships&lt;/li&gt;
&lt;li&gt;settlement infrastructure&lt;/li&gt;
&lt;li&gt;compliance operations&lt;/li&gt;
&lt;li&gt;payment processing agreements&lt;/li&gt;
&lt;li&gt;treasury management systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most startups, that was simply out of reach.&lt;/p&gt;

&lt;p&gt;Today, things look very different.&lt;/p&gt;

&lt;p&gt;A small team can build products that move money globally without owning a bank, operating payment rails, or maintaining treasury infrastructure.&lt;/p&gt;

&lt;p&gt;The secret isn't magic.&lt;/p&gt;

&lt;p&gt;It's infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Fintech Playbook
&lt;/h2&gt;

&lt;p&gt;Historically, launching a financial product meant becoming part of the financial system itself.&lt;/p&gt;

&lt;p&gt;That often involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;obtaining licenses&lt;/li&gt;
&lt;li&gt;negotiating banking partnerships&lt;/li&gt;
&lt;li&gt;integrating with payment processors&lt;/li&gt;
&lt;li&gt;maintaining settlement accounts&lt;/li&gt;
&lt;li&gt;building compliance operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The barriers were high.&lt;/p&gt;

&lt;p&gt;And for good reason.&lt;/p&gt;

&lt;p&gt;Moving money is complicated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Fintech Stack
&lt;/h2&gt;

&lt;p&gt;Modern fintech companies increasingly build on top of specialized infrastructure providers.&lt;/p&gt;

&lt;p&gt;Instead of building everything themselves, they compose financial primitives.&lt;/p&gt;

&lt;p&gt;Think of it like cloud computing.&lt;/p&gt;

&lt;p&gt;Most startups don't build their own data centers.&lt;/p&gt;

&lt;p&gt;They build on AWS, Google Cloud, or Azure.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is moving in a similar direction.&lt;/p&gt;

&lt;p&gt;Developers now have access to building blocks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;virtual accounts&lt;/li&gt;
&lt;li&gt;wallet infrastructure&lt;/li&gt;
&lt;li&gt;payouts&lt;/li&gt;
&lt;li&gt;foreign exchange&lt;/li&gt;
&lt;li&gt;stablecoin settlement&lt;/li&gt;
&lt;li&gt;transaction monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;through APIs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift from Banks to Building Blocks
&lt;/h2&gt;

&lt;p&gt;Consider a simple requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Receive money from customers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Years ago, that might require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direct banking integrations&lt;/li&gt;
&lt;li&gt;account provisioning systems&lt;/li&gt;
&lt;li&gt;settlement infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, an API can provision receiving accounts for you.&lt;/p&gt;

&lt;p&gt;The same pattern appears across the financial stack.&lt;/p&gt;

&lt;p&gt;Need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;receive funds?&lt;/li&gt;
&lt;li&gt;send payouts?&lt;/li&gt;
&lt;li&gt;manage wallets?&lt;/li&gt;
&lt;li&gt;support multiple currencies?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are infrastructure layers designed to handle those complexities.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Developers Actually Build
&lt;/h2&gt;

&lt;p&gt;The interesting thing is that most fintech startups are not building payment rails.&lt;/p&gt;

&lt;p&gt;They're building products.&lt;/p&gt;

&lt;p&gt;The rails already exist.&lt;/p&gt;

&lt;p&gt;The real value often comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user experience&lt;/li&gt;
&lt;li&gt;automation&lt;/li&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;business logic&lt;/li&gt;
&lt;li&gt;distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not from rebuilding banking infrastructure.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A freelancer platform doesn't need to become a bank.&lt;/p&gt;

&lt;p&gt;It needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer onboarding&lt;/li&gt;
&lt;li&gt;collections&lt;/li&gt;
&lt;li&gt;payouts&lt;/li&gt;
&lt;li&gt;reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure providers handle the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Role of Stablecoins
&lt;/h2&gt;

&lt;p&gt;Stablecoins are accelerating this trend.&lt;/p&gt;

&lt;p&gt;Many cross-border payment platforms now use stablecoins internally for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;treasury management&lt;/li&gt;
&lt;li&gt;liquidity movement&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while customers continue interacting with familiar payment experiences.&lt;/p&gt;

&lt;p&gt;The customer sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send Money
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure may involve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Collection
↓
Settlement
↓
Stablecoin Treasury
↓
Liquidity Partner
↓
Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complexity is hidden behind APIs and infrastructure layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Builders
&lt;/h2&gt;

&lt;p&gt;The question is no longer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I become a bank?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What can I build on top of financial infrastructure?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That opens the door to entirely new products.&lt;/p&gt;

&lt;p&gt;Developers can focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;marketplaces&lt;/li&gt;
&lt;li&gt;payroll systems&lt;/li&gt;
&lt;li&gt;creator platforms&lt;/li&gt;
&lt;li&gt;remittance products&lt;/li&gt;
&lt;li&gt;B2B payment workflows&lt;/li&gt;
&lt;li&gt;treasury automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without spending years building foundational infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infrastructure Is Becoming the Advantage
&lt;/h2&gt;

&lt;p&gt;The most successful fintech companies of the next decade may not be the ones that own the rails.&lt;/p&gt;

&lt;p&gt;They may be the ones that use the rails most effectively.&lt;/p&gt;

&lt;p&gt;Just as cloud providers abstracted away servers, modern financial infrastructure is abstracting away much of the complexity involved in moving money.&lt;/p&gt;

&lt;p&gt;That allows builders to focus on solving customer problems rather than rebuilding the financial system from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a global payment product used to require becoming part of the banking system.&lt;/p&gt;

&lt;p&gt;Today, developers can access many of the same capabilities through infrastructure providers and APIs.&lt;/p&gt;

&lt;p&gt;The opportunity has shifted.&lt;/p&gt;

&lt;p&gt;Less time spent building rails.&lt;/p&gt;

&lt;p&gt;More time spent building products.&lt;/p&gt;

&lt;p&gt;And that might be one of the most important changes happening in fintech today.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>payment</category>
      <category>banking</category>
    </item>
    <item>
      <title>Accept Payments in Minutes with Afriex Checkout Sessions</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Sat, 30 May 2026 16:48:17 +0000</pubDate>
      <link>https://dev.to/afriex/accept-payments-in-minutes-with-afriex-checkout-sessions-3j1m</link>
      <guid>https://dev.to/afriex/accept-payments-in-minutes-with-afriex-checkout-sessions-3j1m</guid>
      <description>&lt;p&gt;Most payment integrations follow the same painful path: you build a checkout UI, set up bank account display logic, handle Mobile Money prompts, manage session timers, poll for confirmation, and somehow do all of this correctly across every edge case. It takes days, sometimes weeks — and that's before you think about testing.&lt;/p&gt;

&lt;p&gt;Afriex Checkout Sessions changes that. With a single API call, you get a fully hosted payment page that handles everything: payment method selection, bank transfer details, Mobile Money authorization, session expiration, and redirect on completion. You stay focused on your product; Afriex handles the payment flow.&lt;/p&gt;

&lt;p&gt;This guide walks you through a complete integration from scratch — using either the official TypeScript SDK or the REST API directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The flow is four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your server&lt;/strong&gt; creates a checkout session via the SDK or REST API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Afriex&lt;/strong&gt; returns a &lt;code&gt;checkoutUrl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You redirect&lt;/strong&gt; the customer to that URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After payment&lt;/strong&gt;, Afriex redirects the customer back to your app and fires a webhook to your server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the entire integration surface. No payment UI to build. No polling loop. No bank account display to manage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you start, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;Afriex Business account&lt;/a&gt; with API access&lt;/li&gt;
&lt;li&gt;Your API key from the &lt;a href="https://business.afriex.com/developer" rel="noopener noreferrer"&gt;Afriex dashboard&lt;/a&gt; (Developer tab)&lt;/li&gt;
&lt;li&gt;Node.js 20+ if you're using the SDK, or any server-side runtime for the REST API&lt;/li&gt;
&lt;li&gt;An HTTPS redirect URL where customers will land after payment&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sandbox first.&lt;/strong&gt; Checkout Sessions is currently available for end-to-end testing in Afriex's sandbox environment. Production rollout is happening soon — use sandbox to validate your integration now so you're ready to flip the switch.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using the SDK (Node.js / TypeScript)
&lt;/h3&gt;

&lt;p&gt;Install the official Afriex SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @afriex/sdk
&lt;span class="c"&gt;# or&lt;/span&gt;
pnpm add @afriex/sdk
&lt;span class="c"&gt;# or&lt;/span&gt;
yarn add @afriex/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then initialize it once — typically in a shared module or service file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AfriexSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@afriex/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AfriexSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or "production"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK maps &lt;code&gt;staging&lt;/code&gt; to &lt;code&gt;https://sandbox.api.afriex.com&lt;/code&gt; and &lt;code&gt;production&lt;/code&gt; to &lt;code&gt;https://api.afriex.com&lt;/code&gt; — no need to manage base URLs yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable retries for production resilience:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AfriexSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// milliseconds&lt;/span&gt;
    &lt;span class="na"&gt;retryableStatusCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the REST API directly
&lt;/h3&gt;

&lt;p&gt;Set your base URL depending on your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sandbox:    https://sandbox.api.afriex.com
Production: https://api.afriex.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass your API key on every request via the &lt;code&gt;x-api-key&lt;/code&gt; header.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Checkout Session
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SDK (Node.js / TypeScript)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./afriex-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// your initialized SDK instance&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountInKobo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// e.g. 500000 for ₦5,000&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// must be unique per session&lt;/span&gt;
    &lt;span class="na"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourapp.com/checkout/return&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIRTUAL_BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// E.164 format: +2348100000000&lt;/span&gt;
      &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkoutUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK returns &lt;code&gt;{ checkoutUrl: string }&lt;/code&gt; — redirect your customer there immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  REST API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  cURL
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://sandbox.api.afriex.com/api/v1/checkout-session &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "amount": 500000,
    "currency": "NGN",
    "merchantReference": "order-2026-05-30-001",
    "redirectUrl": "https://yourapp.com/checkout/return",
    "channels": ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
    "customer": {
      "name": "Amarachi Okafor",
      "email": "amara@example.com",
      "phone": "+2348100000000",
      "countryCode": "NG"
    },
    "metadata": {
      "orderId": "ORD-9912",
      "plan": "pro"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Node.js (fetch)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://sandbox.api.afriex.com/api/v1/checkout-session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountInKobo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourapp.com/checkout/return&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIRTUAL_BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkoutUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Python (requests)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_checkout_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://sandbox.api.afriex.com/api/v1/checkout-session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount_in_kobo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;currency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NGN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;merchantReference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redirectUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/checkout/return&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VIRTUAL_BANK_ACCOUNT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;countryCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;checkoutUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Response
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checkoutUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://sandbox.pay.afriex.com/pay/eyJhbGciOiJI..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redirect your customer to &lt;code&gt;checkoutUrl&lt;/code&gt; immediately. The session has an expiration timer, so don't hold on to the URL for later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Request Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Required Fields
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;amount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Amount in &lt;strong&gt;minor currency units&lt;/strong&gt; (kobo for NGN, cents for USD). Minimum: &lt;code&gt;100&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;currency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;ISO 4217 currency code, uppercase (e.g. &lt;code&gt;NGN&lt;/code&gt;, &lt;code&gt;GHS&lt;/code&gt;). Must be enabled for checkout on your account.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merchantReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Your unique identifier for this session. Used to match webhooks and reconcile transactions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redirectUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;HTTPS URL the customer is sent to after the checkout flow completes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;Customer details. See below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Customer Object
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Customer's full name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;email&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Customer's email address.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;phone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Phone number in E.164 format (e.g. &lt;code&gt;+2348100000000&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;countryCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;ISO 3166-1 alpha-2 country code (e.g. &lt;code&gt;NG&lt;/code&gt;, &lt;code&gt;GH&lt;/code&gt;). Case-insensitive.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Optional Fields
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["VIRTUAL_BANK_ACCOUNT"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payment methods to offer. Options: &lt;code&gt;VIRTUAL_BANK_ACCOUNT&lt;/code&gt;, &lt;code&gt;MOBILE_MONEY&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Flat key/value pairs (strings only) you want attached to the session for your own reference.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 2: The Customer Completes Payment
&lt;/h2&gt;

&lt;p&gt;Once redirected, the customer lands on the Afriex-hosted checkout page. Depending on the &lt;code&gt;channels&lt;/code&gt; you enabled, they can:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pay by Bank Transfer
&lt;/h3&gt;

&lt;p&gt;The customer sees a one-time virtual bank account — account name, number, and bank — with the exact amount to transfer. They open their banking app, send the transfer, and return to the checkout page. Afriex detects the inbound transfer and confirms payment automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Exact amount required.&lt;/strong&gt; The customer must transfer the precise amount shown. Sending a different amount will cause delays and may require a manual reconciliation or refund.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pay by Mobile Money
&lt;/h3&gt;

&lt;p&gt;The customer selects their network (e.g. MTN, Airtel), enters their account name and phone number, and taps &lt;strong&gt;Pay now&lt;/strong&gt;. Afriex pushes an authorization prompt to their phone. Once they approve it, the checkout page updates automatically.&lt;/p&gt;

&lt;p&gt;Both paths end in one of two outcomes: a &lt;strong&gt;success screen&lt;/strong&gt; (auto-redirects to your &lt;code&gt;redirectUrl&lt;/code&gt;) or a &lt;strong&gt;failure screen&lt;/strong&gt; (with guidance to retry or contact support).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Handle the Redirect
&lt;/h2&gt;

&lt;p&gt;When the customer lands back on your &lt;code&gt;redirectUrl&lt;/code&gt;, show them an appropriate confirmation or error screen. Use the &lt;code&gt;merchantReference&lt;/code&gt; you generated to look up the order state in your system.&lt;/p&gt;

&lt;p&gt;A typical return handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express.js example&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/checkout/return&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The merchantReference you passed when creating the session&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Look up your order by reference — don't trust query params for payment status&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/orders/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/confirmation`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Not yet confirmed — webhook may still be in flight&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/orders/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pending`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't use the redirect to confirm payment.&lt;/strong&gt; The redirect tells you the customer finished the checkout flow, not that payment succeeded. Always use the webhook (see Step 4) as the authoritative signal.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4: Listen for the Webhook
&lt;/h2&gt;

&lt;p&gt;When Afriex confirms payment, it fires a &lt;code&gt;CHECKOUT_SESSION.CREATED&lt;/code&gt; webhook to the callback URL configured in your Afriex dashboard. This is the source of truth for marking an order as paid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express.js webhook handler example&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHECKOUT_SESSION.CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Mark order as paid using your merchantReference&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make your webhook handler idempotent — Afriex may retry delivery if your endpoint doesn't respond with a &lt;code&gt;200&lt;/code&gt;. Using &lt;code&gt;merchantReference&lt;/code&gt; as your idempotency key is the right approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond the Basic Flow: Advanced Use Cases
&lt;/h2&gt;

&lt;p&gt;The checkout session API supports a few patterns beyond the standard redirect flow that are worth knowing about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delayed Payment Collection
&lt;/h3&gt;

&lt;p&gt;You don't have to redirect customers immediately after creating a session. You can create the session on your server, store the &lt;code&gt;checkoutUrl&lt;/code&gt;, and send it to the customer later — via email, SMS, or a payment link in an invoice. This is useful for billing flows where payment isn't expected at the point of order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create the session when the invoice is generated&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;invoiceDetails&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Store checkoutUrl against the invoice&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;paymentUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkoutUrl&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Send it later when the invoice is dispatched&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your invoice is ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Pay here: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep session expiration in mind — if the payment link will be sent days later, build in a check for whether the session is still valid before sending.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedded Checkout (Iframe / Webview)
&lt;/h3&gt;

&lt;p&gt;Rather than fully leaving your app, you can open the &lt;code&gt;checkoutUrl&lt;/code&gt; inside an iframe (web) or a webview (mobile). This keeps the customer inside your product's shell while Afriex handles the payment UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Web: iframe embed --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://sandbox.pay.afriex.com/pay/YOUR_SESSION_TOKEN"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt;
  &lt;span class="na"&gt;frameborder=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;
  &lt;span class="na"&gt;allow=&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React Native: WebView embed&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebView&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-webview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WebView&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkoutUrl&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="nx"&gt;onNavigationStateChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Detect redirect back to your redirectUrl&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourapp.com/checkout/return&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OrderConfirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook still fires regardless of how the customer accesses the checkout — embedded or full redirect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Amounts are always in minor units
&lt;/h3&gt;

&lt;p&gt;Afriex uses the smallest denomination of each currency — no decimals in the &lt;code&gt;amount&lt;/code&gt; field:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Currency&lt;/th&gt;
&lt;th&gt;Major unit&lt;/th&gt;
&lt;th&gt;Minor unit&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NGN&lt;/td&gt;
&lt;td&gt;₦1&lt;/td&gt;
&lt;td&gt;1 kobo&lt;/td&gt;
&lt;td&gt;₦5,000 → &lt;code&gt;500000&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GHS&lt;/td&gt;
&lt;td&gt;₵1&lt;/td&gt;
&lt;td&gt;1 pesewa&lt;/td&gt;
&lt;td&gt;₵100 → &lt;code&gt;10000&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USD&lt;/td&gt;
&lt;td&gt;$1&lt;/td&gt;
&lt;td&gt;1 cent&lt;/td&gt;
&lt;td&gt;$10 → &lt;code&gt;1000&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The minimum &lt;code&gt;amount&lt;/code&gt; is &lt;code&gt;100&lt;/code&gt; (equivalent to 1 major unit).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;merchantReference&lt;/code&gt; must be unique
&lt;/h3&gt;

&lt;p&gt;Each session needs a distinct &lt;code&gt;merchantReference&lt;/code&gt;. Re-using a reference from a previous session will result in an error. Use your internal order ID, a UUID, or any other identifier you control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sessions expire
&lt;/h3&gt;

&lt;p&gt;Each checkout session has an expiration timer visible to the customer. If the session expires before payment completes, the customer will need a new session. Build a recovery path — for example, let customers re-initiate checkout from your order page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phone numbers must be E.164
&lt;/h3&gt;

&lt;p&gt;The customer's &lt;code&gt;phone&lt;/code&gt; must be in E.164 format: &lt;code&gt;+&lt;/code&gt; followed by the country code and subscriber number, no spaces or punctuation. For example, a Nigerian number would be &lt;code&gt;+2348100000000&lt;/code&gt;, not &lt;code&gt;08100000000&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing in Sandbox
&lt;/h2&gt;

&lt;p&gt;All of the above works end-to-end in sandbox today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use base URL &lt;code&gt;https://sandbox.api.afriex.com&lt;/code&gt; (or set &lt;code&gt;environment: "staging"&lt;/code&gt; in the SDK)&lt;/li&gt;
&lt;li&gt;Use your sandbox API key from the Afriex dashboard&lt;/li&gt;
&lt;li&gt;Successful checkout sessions redirect to &lt;code&gt;https://sandbox.pay.afriex.com/pay/{token}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Webhooks fire to your configured callback URL in sandbox as well&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test the full round-trip — create a session, go through the hosted checkout, and verify your webhook handler receives and processes the event — before switching to production credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error Responses
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;400&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Invalid request — check required fields, amount minimum, and currency format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unauthorized — verify your &lt;code&gt;x-api-key&lt;/code&gt; header&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Server error — retry with exponential backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://afriex-sdk-docs.vercel.app/getting-started" rel="noopener noreferrer"&gt;Afriex SDK Documentation&lt;/a&gt;&lt;/strong&gt; — Full SDK reference covering installation, configuration, and all available modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://afriex-sdk-docs.vercel.app/api/checkout" rel="noopener noreferrer"&gt;SDK Checkout Reference&lt;/a&gt;&lt;/strong&gt; — SDK-specific parameters and examples for the Checkout API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.afriex.com/api-reference/endpoint/checkout-sessions/user-flow-guide" rel="noopener noreferrer"&gt;User Flow Guide&lt;/a&gt;&lt;/strong&gt; — See the full customer-facing checkout experience, screen by screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.afriex.com/api-reference/endpoint/webhooks/introduction" rel="noopener noreferrer"&gt;Webhooks&lt;/a&gt;&lt;/strong&gt; — Configure your callback URL and understand the full webhook payload&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.afriex.com/api-reference/endpoint/checkout-sessions/create" rel="noopener noreferrer"&gt;REST API Reference&lt;/a&gt;&lt;/strong&gt; — Full OpenAPI spec for the Create Checkout Session endpoint&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Afriex Checkout Sessions is currently available in sandbox. Production rollout is expected in the coming weeks — get your integration ready today.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>stripe</category>
      <category>fintech</category>
      <category>crossborder</category>
    </item>
    <item>
      <title>What Happens After You Click “Send Money”? Lessons from the Afriex Utila Partnership</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Fri, 29 May 2026 15:42:42 +0000</pubDate>
      <link>https://dev.to/afriex/what-happens-after-you-click-send-money-lessons-from-the-afriex-x-utila-partnership-3o1d</link>
      <guid>https://dev.to/afriex/what-happens-after-you-click-send-money-lessons-from-the-afriex-x-utila-partnership-3o1d</guid>
      <description>&lt;p&gt;Most people think cross-border payments are simple.&lt;/p&gt;

&lt;p&gt;You open an app.&lt;/p&gt;

&lt;p&gt;Enter an amount.&lt;/p&gt;

&lt;p&gt;Choose a recipient.&lt;/p&gt;

&lt;p&gt;Click send.&lt;/p&gt;

&lt;p&gt;A few moments later, the money arrives.&lt;/p&gt;

&lt;p&gt;Simple.&lt;/p&gt;

&lt;p&gt;At least on the surface.&lt;/p&gt;

&lt;p&gt;What most people never see is the infrastructure required to make that experience possible at scale.&lt;/p&gt;

&lt;p&gt;Recently, Utila published a case study about its partnership with Afriex, a company operating cross-border payment corridors across Africa. While the announcement focused on infrastructure, it revealed something much more interesting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The real challenge in cross-border payments isn't sending money. It's managing liquidity, treasury operations, and settlement across multiple markets simultaneously.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's where stablecoins are quietly becoming critical infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Side of Cross-Border Payments
&lt;/h2&gt;

&lt;p&gt;When users think about a transfer, they typically imagine something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender
   ↓
Payment
   ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reality looks closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender
   ↓
Collection Rail
   ↓
Treasury Layer
   ↓
Liquidity Partner
   ↓
FX Conversion
   ↓
Settlement Layer
   ↓
Local Payout Partner
   ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every additional country introduces more complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different currencies&lt;/li&gt;
&lt;li&gt;different banking systems&lt;/li&gt;
&lt;li&gt;different payout partners&lt;/li&gt;
&lt;li&gt;different liquidity requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now imagine operating this across more than 20 African corridors.&lt;/p&gt;

&lt;p&gt;The challenge quickly becomes operational.&lt;/p&gt;




&lt;h2&gt;
  
  
  Afriex's Challenge
&lt;/h2&gt;

&lt;p&gt;According to Utila's case study, Afriex needed the ability to support multiple stablecoins across multiple blockchain networks while settling with payout partners throughout Africa.&lt;/p&gt;

&lt;p&gt;The problem wasn't simply storing assets.&lt;/p&gt;

&lt;p&gt;It was managing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USDC&lt;/li&gt;
&lt;li&gt;USDT&lt;/li&gt;
&lt;li&gt;multiple blockchain networks&lt;/li&gt;
&lt;li&gt;treasury movements&lt;/li&gt;
&lt;li&gt;partner settlements&lt;/li&gt;
&lt;li&gt;growing B2B customer operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;all from a unified operational layer.&lt;/p&gt;

&lt;p&gt;Without consolidation, these workflows often require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple exchanges&lt;/li&gt;
&lt;li&gt;multiple wallets&lt;/li&gt;
&lt;li&gt;manual treasury processes&lt;/li&gt;
&lt;li&gt;fragmented operational visibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As transaction volume grows, that model becomes increasingly difficult to manage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Stablecoins Matter
&lt;/h2&gt;

&lt;p&gt;When most people hear "stablecoins," they think about crypto.&lt;/p&gt;

&lt;p&gt;But infrastructure teams increasingly think about them differently.&lt;/p&gt;

&lt;p&gt;They think about settlement.&lt;/p&gt;

&lt;p&gt;Stablecoins offer something traditional payment systems often struggle to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global interoperability&lt;/li&gt;
&lt;li&gt;24/7 settlement&lt;/li&gt;
&lt;li&gt;programmable transfers&lt;/li&gt;
&lt;li&gt;asset portability across networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For payment companies, stablecoins are increasingly functioning as operational rails rather than speculative assets.&lt;/p&gt;

&lt;p&gt;That's a significant shift.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Infrastructure Layer
&lt;/h2&gt;

&lt;p&gt;One detail from the case study stood out.&lt;/p&gt;

&lt;p&gt;Afriex needed to support:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;any stablecoin on any chain&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;for partner settlements across African markets.&lt;/p&gt;

&lt;p&gt;That sounds simple.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Different counterparties may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different assets&lt;/li&gt;
&lt;li&gt;different networks&lt;/li&gt;
&lt;li&gt;different settlement preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting that flexibility often introduces substantial operational complexity.&lt;/p&gt;

&lt;p&gt;According to the case study, Utila provided a single platform that enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-asset support&lt;/li&gt;
&lt;li&gt;multi-chain support&lt;/li&gt;
&lt;li&gt;treasury operations&lt;/li&gt;
&lt;li&gt;wallet provisioning&lt;/li&gt;
&lt;li&gt;settlement workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;from one operational layer.&lt;/p&gt;

&lt;p&gt;The result was less fragmentation and greater operational control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond Treasury: Wallet Infrastructure
&lt;/h2&gt;

&lt;p&gt;Another interesting outcome was wallet provisioning.&lt;/p&gt;

&lt;p&gt;As Afriex expanded its B2B platform, business customers needed dedicated wallet infrastructure.&lt;/p&gt;

&lt;p&gt;Rather than treating wallets as an internal treasury tool, wallets became part of the customer-facing product experience.&lt;/p&gt;

&lt;p&gt;Businesses could receive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dedicated wallet addresses&lt;/li&gt;
&lt;li&gt;preferred stablecoin support&lt;/li&gt;
&lt;li&gt;preferred network support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while Afriex managed the infrastructure centrally.&lt;/p&gt;

&lt;p&gt;This highlights an important trend:&lt;/p&gt;

&lt;p&gt;Stablecoin infrastructure is increasingly becoming product infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Trend
&lt;/h2&gt;

&lt;p&gt;The Afriex × Utila story reflects a broader industry movement.&lt;/p&gt;

&lt;p&gt;Stablecoins are no longer only consumer products.&lt;/p&gt;

&lt;p&gt;They are becoming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;treasury infrastructure&lt;/li&gt;
&lt;li&gt;settlement infrastructure&lt;/li&gt;
&lt;li&gt;liquidity infrastructure&lt;/li&gt;
&lt;li&gt;payment infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Across the payments industry, companies are increasingly evaluating stablecoins as an alternative settlement layer because of their speed, global accessibility, and operational flexibility.&lt;/p&gt;

&lt;p&gt;In many ways, stablecoins are evolving into the backend infrastructure powering modern money movement.&lt;/p&gt;

&lt;p&gt;Users may never see them.&lt;/p&gt;

&lt;p&gt;But they increasingly benefit from them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;The most interesting takeaway from the Afriex × Utila partnership isn't that a fintech adopted stablecoins.&lt;/p&gt;

&lt;p&gt;It's that operational complexity becomes the biggest challenge as payment systems scale.&lt;/p&gt;

&lt;p&gt;Building a payment product is one thing.&lt;/p&gt;

&lt;p&gt;Operating liquidity, settlements, treasury, and customer infrastructure across dozens of markets is another challenge entirely.&lt;/p&gt;

&lt;p&gt;The companies that succeed are often the ones that find ways to simplify that complexity.&lt;/p&gt;

&lt;p&gt;That's what makes this case study interesting.&lt;/p&gt;

&lt;p&gt;It's a reminder that behind every successful payment experience is an enormous amount of infrastructure working quietly in the background.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Cross-border payments are often discussed from the perspective of user experience.&lt;/p&gt;

&lt;p&gt;Faster transfers.&lt;/p&gt;

&lt;p&gt;Lower fees.&lt;/p&gt;

&lt;p&gt;Better access.&lt;/p&gt;

&lt;p&gt;But infrastructure tells a different story.&lt;/p&gt;

&lt;p&gt;The real challenge is coordinating liquidity, settlements, treasury operations, and customer assets across a fragmented financial landscape.&lt;/p&gt;

&lt;p&gt;The Afriex × Utila partnership offers a glimpse into how modern payment companies are approaching that challenge.&lt;/p&gt;

&lt;p&gt;And increasingly, stablecoins are becoming part of the answer.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>cryptocurrency</category>
      <category>infrastructure</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>What Happens When AI Agents Can Access Payment Infrastructure? Exploring OpenClaw + Afriex MCP</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Fri, 29 May 2026 15:24:25 +0000</pubDate>
      <link>https://dev.to/afriex/what-happens-when-ai-agents-can-access-payment-infrastructure-exploring-openclaw-afriex-mcp-381e</link>
      <guid>https://dev.to/afriex/what-happens-when-ai-agents-can-access-payment-infrastructure-exploring-openclaw-afriex-mcp-381e</guid>
      <description>&lt;p&gt;For years, we've built APIs for developers.&lt;/p&gt;

&lt;p&gt;Every payment gateway, banking platform, fintech API, and infrastructure provider has been designed around a simple assumption:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A human developer writes the code that interacts with the API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what happens when AI agents become the users of those APIs?&lt;/p&gt;

&lt;p&gt;That's the question I've been thinking about while exploring OpenClaw and Afriex MCP.&lt;/p&gt;

&lt;p&gt;Not from the perspective of replacing developers.&lt;/p&gt;

&lt;p&gt;But from the perspective of enabling AI systems to interact with infrastructure in meaningful ways.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift From Code Generation to Infrastructure Interaction
&lt;/h2&gt;

&lt;p&gt;Most developers have already experienced AI-assisted coding.&lt;/p&gt;

&lt;p&gt;We use tools like Cursor to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate components&lt;/li&gt;
&lt;li&gt;write tests&lt;/li&gt;
&lt;li&gt;explain code&lt;/li&gt;
&lt;li&gt;scaffold projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that's still fundamentally code generation.&lt;/p&gt;

&lt;p&gt;The AI helps create software.&lt;/p&gt;

&lt;p&gt;The developer remains the bridge between the software and the infrastructure.&lt;/p&gt;

&lt;p&gt;The next evolution is different.&lt;/p&gt;

&lt;p&gt;The AI doesn't just generate code.&lt;/p&gt;

&lt;p&gt;The AI interacts with infrastructure directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter OpenClaw
&lt;/h2&gt;

&lt;p&gt;OpenClaw is an open framework for building AI agents capable of interacting with tools and external systems.&lt;/p&gt;

&lt;p&gt;Instead of simply responding to prompts, agents can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;perform actions&lt;/li&gt;
&lt;li&gt;execute workflows&lt;/li&gt;
&lt;li&gt;use tools&lt;/li&gt;
&lt;li&gt;interact with services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This transforms the AI from a conversational assistant into something much more operational.&lt;/p&gt;

&lt;p&gt;The interesting question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What tools should these agents have access to?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's where infrastructure enters the picture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter Afriex MCP
&lt;/h2&gt;

&lt;p&gt;Afriex MCP exposes Afriex capabilities through the Model Context Protocol.&lt;/p&gt;

&lt;p&gt;This means AI-enabled tools and agents can interact with payment infrastructure through structured tooling.&lt;/p&gt;

&lt;p&gt;Instead of building everything manually first, an agent can understand and interact with financial primitives such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;balances&lt;/li&gt;
&lt;li&gt;virtual accounts&lt;/li&gt;
&lt;li&gt;transactions&lt;/li&gt;
&lt;li&gt;payment workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;through MCP-enabled interfaces.&lt;/p&gt;

&lt;p&gt;On their own, OpenClaw and Afriex MCP are interesting.&lt;/p&gt;

&lt;p&gt;Together, they become much more compelling.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Could an Infrastructure-Aware Agent Do?
&lt;/h2&gt;

&lt;p&gt;Imagine an AI agent with access to payment infrastructure.&lt;/p&gt;

&lt;p&gt;Not in a hypothetical science-fiction future.&lt;/p&gt;

&lt;p&gt;Today.&lt;/p&gt;




&lt;h2&gt;
  
  
  Balance Monitoring
&lt;/h2&gt;

&lt;p&gt;An agent could continuously monitor balances across accounts.&lt;/p&gt;

&lt;p&gt;When thresholds are reached, it could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notify teams&lt;/li&gt;
&lt;li&gt;trigger workflows&lt;/li&gt;
&lt;li&gt;generate reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without manual intervention.&lt;/p&gt;




&lt;h2&gt;
  
  
  Payment Operations
&lt;/h2&gt;

&lt;p&gt;An agent could generate receiving instructions for customers.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a virtual account for Customer X.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent retrieves the required information and returns payment instructions immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transaction Monitoring
&lt;/h2&gt;

&lt;p&gt;Instead of manually checking dashboards, agents could monitor transaction events and surface only what requires human attention.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;failed transactions&lt;/li&gt;
&lt;li&gt;delayed settlements&lt;/li&gt;
&lt;li&gt;unusual activity&lt;/li&gt;
&lt;li&gt;reconciliation issues&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Developer Workflows
&lt;/h2&gt;

&lt;p&gt;This is the area that excites me most.&lt;/p&gt;

&lt;p&gt;Imagine asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate a payment integration using Afriex.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of simply generating generic code, the agent understands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the infrastructure&lt;/li&gt;
&lt;li&gt;available tools&lt;/li&gt;
&lt;li&gt;workflow patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and builds with real context.&lt;/p&gt;

&lt;p&gt;The difference is subtle.&lt;/p&gt;

&lt;p&gt;But significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  APIs Were Built for Developers
&lt;/h2&gt;

&lt;p&gt;One idea keeps coming back to me.&lt;/p&gt;

&lt;p&gt;For decades, we've designed APIs around human developers.&lt;/p&gt;

&lt;p&gt;Documentation.&lt;br&gt;
SDKs.&lt;br&gt;
Authentication.&lt;br&gt;
Request-response patterns.&lt;/p&gt;

&lt;p&gt;Everything assumes a human sits in the middle.&lt;/p&gt;

&lt;p&gt;But MCP introduces a different model.&lt;/p&gt;

&lt;p&gt;Infrastructure becomes accessible not only to developers, but also to AI systems.&lt;/p&gt;

&lt;p&gt;That changes how we think about integrations.&lt;/p&gt;

&lt;p&gt;It changes how we think about tooling.&lt;/p&gt;

&lt;p&gt;And eventually, it may change how software gets built.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Fintech Is Particularly Interesting
&lt;/h2&gt;

&lt;p&gt;Many industries can benefit from infrastructure-aware agents.&lt;/p&gt;

&lt;p&gt;Fintech stands out because financial workflows are already highly structured.&lt;/p&gt;

&lt;p&gt;Consider how much time teams spend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monitoring transactions&lt;/li&gt;
&lt;li&gt;checking balances&lt;/li&gt;
&lt;li&gt;reconciling payments&lt;/li&gt;
&lt;li&gt;handling operational workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are precisely the kinds of activities agents can assist with.&lt;/p&gt;

&lt;p&gt;Not replacing humans.&lt;/p&gt;

&lt;p&gt;Augmenting them.&lt;/p&gt;

&lt;p&gt;Making teams more efficient.&lt;/p&gt;

&lt;p&gt;Reducing operational friction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;I don't think the most interesting outcome is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI writes code faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think the more interesting outcome is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI understands and interacts with infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a much bigger shift.&lt;/p&gt;

&lt;p&gt;Because once agents can interact with infrastructure safely and predictably, entirely new workflows become possible.&lt;/p&gt;

&lt;p&gt;OpenClaw provides the agent layer.&lt;/p&gt;

&lt;p&gt;Afriex MCP provides the infrastructure layer.&lt;/p&gt;

&lt;p&gt;Together they offer a glimpse into what AI-native financial systems might look like.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;We're still early.&lt;/p&gt;

&lt;p&gt;Most of these patterns are only beginning to emerge.&lt;/p&gt;

&lt;p&gt;But one thing feels increasingly clear:&lt;/p&gt;

&lt;p&gt;The future isn't just AI-assisted development.&lt;/p&gt;

&lt;p&gt;It's infrastructure-aware AI.&lt;/p&gt;

&lt;p&gt;And as developers, it's worth paying attention to what becomes possible when agents can interact with the systems we've spent years building.&lt;/p&gt;

&lt;p&gt;OpenClaw and Afriex MCP are an interesting place to start exploring that future.&lt;/p&gt;




</description>
      <category>mcp</category>
      <category>openclaw</category>
      <category>afriex</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Afriex vs Flutterwave vs Paystack: Which One Should You Choose?</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Mon, 25 May 2026 12:56:13 +0000</pubDate>
      <link>https://dev.to/afriex/afriex-vs-flutterwave-vs-paystack-which-one-should-you-use-for-payouts-and-disbursements-20jc</link>
      <guid>https://dev.to/afriex/afriex-vs-flutterwave-vs-paystack-which-one-should-you-use-for-payouts-and-disbursements-20jc</guid>
      <description>&lt;p&gt;If you are building something that collects payments from customers, Paystack and Flutterwave are both solid choices and you probably already know that. This article is not about that.&lt;/p&gt;

&lt;p&gt;This is about the other direction: sending money out. Paying a freelancer in Nairobi from a USD balance. Disbursing contractor fees to someone in Accra via MTN Mobile Money. Running payroll across five African countries without building a separate integration for each one. That is a different problem, and it needs a different tool.&lt;/p&gt;

&lt;p&gt;The honest answer is that Paystack and Flutterwave were built collection-first. They handle disbursements, but with limits that matter once you are operating across borders. Afriex was built disbursement-first, for African corridors specifically. The comparison is not that one is better overall — it is that they solve different problems, and picking the wrong one for outbound payments means hitting walls you did not expect.&lt;/p&gt;

&lt;p&gt;Here is how they actually compare.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we are comparing
&lt;/h2&gt;

&lt;p&gt;Three things matter for outbound disbursement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage&lt;/strong&gt; — which countries and payment channels can you actually pay out to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer experience&lt;/strong&gt; — how the API is structured, what you need to do before money can move, how status updates work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fit for the use case&lt;/strong&gt; — whether the tool was designed for this job or adapted to it.&lt;/p&gt;

&lt;p&gt;We are not comparing collection features, pricing pages, or dashboard UX. Those are separate decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Coverage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Paystack
&lt;/h3&gt;

&lt;p&gt;Paystack is currently available for companies based in Nigeria, South Africa, Ghana, Kenya, and Côte d'Ivoire, supporting NGN, GHS, ZAR, KES, and USD (USD only in Kenya and Nigeria).&lt;/p&gt;

&lt;p&gt;For outbound transfers, Paystack supports bank account payouts within those same markets. It primarily settles in local currencies — if someone pays in USD via a foreign card, funds are often converted to NGN or another local currency. USD payout to a recipient abroad is not a native flow.&lt;/p&gt;

&lt;p&gt;If your recipients are Nigerian businesses with local NGN bank accounts and you are already using Paystack for collections, the transfers API works and settlement is fast. The moment your disbursement needs go outside Nigeria, Ghana, Kenya, or South Africa — or you need to pay in USD without conversion — Paystack starts to show its limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flutterwave
&lt;/h3&gt;

&lt;p&gt;Flutterwave is supported in Nigeria, Ghana, Kenya, South Africa, Uganda, Tanzania, the United Kingdom, the United States, and Europe. It processes payouts in 30+ currencies and has broader mobile money coverage than Paystack across East and West Africa.&lt;/p&gt;

&lt;p&gt;Flutterwave is the wider net of the two, and for businesses that need to pay out in markets like Uganda and Tanzania where Paystack does not operate, it is the more practical choice. The transfers API is well-documented and the mobile money coverage is real.&lt;/p&gt;

&lt;p&gt;The gap is that Flutterwave is still fundamentally a collection platform that also does transfers. The payout flow works, but the API was not designed with multi-country disbursement as its primary job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex
&lt;/h3&gt;

&lt;p&gt;Afriex covers &lt;a href="https://docs.afriex.com/api-reference/supported-currencies" rel="noopener noreferrer"&gt;31 African countries&lt;/a&gt; with payout rails across bank accounts, mobile money (MTN MoMo, M-Pesa, Airtel Money, and others), and SWIFT. Beyond Africa, USD SWIFT payouts reach 100 countries globally, including the US, UK, all of Europe, India, China, Canada, and more. Payment channels include bank transfers, mobile money, SWIFT, UPI (India), Interac (Canada), WeChat Pay and Alipay (China), and crypto (USDC/USDT).&lt;/p&gt;

&lt;p&gt;The coverage table for African countries specifically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Payout Rails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nigeria&lt;/td&gt;
&lt;td&gt;Bank Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kenya&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money, Paybill/Till&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghana&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uganda&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tanzania&lt;/td&gt;
&lt;td&gt;Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cameroon&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Côte d'Ivoire&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethiopia&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egypt&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;South Africa&lt;/td&gt;
&lt;td&gt;Bank Account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rwanda&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Senegal&lt;/td&gt;
&lt;td&gt;Bank Account, Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;+ 19 more African countries&lt;/td&gt;
&lt;td&gt;Mobile Money&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference is not just the number of countries. It is that mobile money is a first-class channel in the Afriex API, not an add-on. In Kenya, you can pay to a bank account, an M-Pesa wallet, or a Paybill/Till number from the same integration. In Ghana, bank and MTN MoMo are both available. For many recipients in these markets, mobile money is their primary financial account, not a fallback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Developer experience
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How Paystack transfers work
&lt;/h3&gt;

&lt;p&gt;Paystack's transfers API follows a straightforward pattern: create a transfer recipient (stores the bank account details), then initiate a transfer to that recipient. The API is clean and the docs are good. The limitation is that it is scoped to Paystack's supported markets, so there is no concept of channel selection — bank account is the primary output.&lt;/p&gt;

&lt;p&gt;For a developer building payroll or contractor payouts strictly within Nigeria, the API is simple and it works. For anything involving mobile money payouts or recipients outside Paystack's five markets, you are building a separate integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Flutterwave transfers work
&lt;/h3&gt;

&lt;p&gt;Flutterwave's transfer API accepts bank account details directly without a separate recipient creation step, which makes it slightly lighter for one-off payouts. Mobile money transfers are supported for several African markets. The documentation covers the major corridors well.&lt;/p&gt;

&lt;p&gt;The gap developers hit in production is reliability. Flutterwave offers broad African coverage across 34 countries with extensive mobile money integration but has more variable API reliability than Paystack or Stripe. For a payroll system where every transfer matters, variable reliability in production is a real constraint to account for.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Afriex transfers work
&lt;/h3&gt;

&lt;p&gt;The Afriex API uses a three-step model: create a customer, attach a payment method, create a transaction. It is slightly more upfront setup than Flutterwave's direct transfer approach, but the structure is deliberate — the payment method record stores verified account details that can be reused across multiple payouts without re-entering them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/v1/customer       → returns customerId
POST /api/v1/payment-method → returns paymentMethodId  
POST /api/v1/transaction    → uses both IDs, returns transactionId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a payroll use case, this is actually the right model. You register an employee once, attach their bank account or mobile wallet once, and then every subsequent payout is a single transaction call with the stored IDs. Repeat disbursements to the same recipient require no additional setup.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;meta.idempotencyKey&lt;/code&gt; field on every transaction prevents duplicates if a job retries after a network failure. The &lt;code&gt;reference&lt;/code&gt; field maps every transaction back to your internal records. Both are required, which enforces good practice.&lt;/p&gt;

&lt;p&gt;Transaction types map cleanly to the three outbound flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WITHDRAW&lt;/code&gt; — send money from your Afriex wallet to a recipient's account&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEPOSIT&lt;/code&gt; — pull money from a customer's account into your wallet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SWAP&lt;/code&gt; — convert between currencies in your Afriex wallet without touching any external account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For disbursement specifically, &lt;code&gt;WITHDRAW&lt;/code&gt; is the one you use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Status updates and webhooks
&lt;/h2&gt;

&lt;p&gt;This is where the tools diverge most practically for anyone building a reliable disbursement system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paystack and Flutterwave
&lt;/h3&gt;

&lt;p&gt;Both support webhooks for transfer status updates. The status vocabulary is standard: pending, success, failed. For domestic transfers that settle quickly, this is sufficient. For cross-border payouts that can sit in intermediate states for longer — especially in corridors with compliance review steps — a simple success/failed model leaves you with less visibility than you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex
&lt;/h3&gt;

&lt;p&gt;The Afriex webhook system fires on &lt;code&gt;TRANSACTION.CREATED&lt;/code&gt; and &lt;code&gt;TRANSACTION.UPDATED&lt;/code&gt;. &lt;br&gt;
Here's the full status vocabulary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What it means for your system&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PENDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Received, not yet processed — do nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROCESSING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Actively moving — do nothing, wait&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;COMPLETED&lt;/code&gt; / &lt;code&gt;SUCCESS&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Settled — notify recipient, update your records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IN_REVIEW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Under compliance review — notify the user, do not mark as failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RETRY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Network is retrying automatically — do nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FAILED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminal failure — alert user, allow retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REJECTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejected after review — alert user with reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REFUNDED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Funds returned — update your records&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Indeterminate — alert your team to investigate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;IN_REVIEW&lt;/code&gt; and &lt;code&gt;RETRY&lt;/code&gt; statuses are the ones that matter most for cross-border corridors. &lt;code&gt;IN_REVIEW&lt;/code&gt; reflects a real compliance hold that African payment networks produce — it is not a failure state, and treating it as one causes support noise for payouts that are actually in progress. &lt;code&gt;RETRY&lt;/code&gt; means the network is handling it without any action needed from your code.&lt;/p&gt;

&lt;p&gt;Webhooks are signed with RSA-SHA256 and verified against your public key from the dashboard. Afriex retries delivery up to 12 times with exponential backoff from 30 seconds to 16 hours, which means a temporary outage on your end does not mean lost status updates.&lt;/p&gt;

&lt;p&gt;The full webhook implementation guide is covered in the &lt;a href="https://dev.to/afriex/how-to-handle-afriex-webhooks-the-right-way"&gt;Afriex webhook article&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use each one
&lt;/h2&gt;

&lt;p&gt;This is the honest summary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Paystack transfers if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your recipients are in Nigeria, Ghana, Kenya, South Africa, or Côte d'Ivoire only&lt;/li&gt;
&lt;li&gt;You are already using Paystack for collection and want a single integration&lt;/li&gt;
&lt;li&gt;Your payout volume is primarily NGN bank transfers with fast local settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Flutterwave transfers if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to pay out in markets Paystack does not cover, like Uganda or Tanzania&lt;/li&gt;
&lt;li&gt;You need mobile money coverage across multiple East and West African markets&lt;/li&gt;
&lt;li&gt;Your use case tolerates some variability in API reliability for the sake of broader coverage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Afriex if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are building a product where disbursement is the primary job: payroll, contractor payouts, scholarship disbursements, creator payouts&lt;/li&gt;
&lt;li&gt;Your recipients are spread across African countries and corridors, including mobile money wallets&lt;/li&gt;
&lt;li&gt;You need to pay out in USD to recipients outside Africa via SWIFT without a separate integration&lt;/li&gt;
&lt;li&gt;You need a detailed transaction status model that maps to how African payment networks actually behave&lt;/li&gt;
&lt;li&gt;You want to store recipient payment method details once and reuse them across multiple payouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleanest setup for a product that does both collection and disbursement is: Paystack or Flutterwave for inbound (they are better positioned for that job), and Afriex for outbound. They are complementary, not competing. Paystack collects your revenue, Afriex pays your contractors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started with Afriex disbursements
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://business.afriex.com/" rel="noopener noreferrer"&gt;Afriex Business API&lt;/a&gt; has a free sandbox environment. No approval gate, no manual credentials process to get through before you can test. Create an account, grab your sandbox API key from &lt;strong&gt;Settings &amp;gt; API Keys&lt;/strong&gt;, and you can run the full disbursement flow — customer creation, payment method attachment, transaction execution, webhook delivery — before you touch production.&lt;/p&gt;

&lt;p&gt;The integration guide covering the full three-step flow with code examples is in the &lt;a href="https://dev.to/afriex/build-a-freelancer-payout-platform-with-the-afriex-sdk-and-nextjs"&gt;freelancer payout platform tutorial&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>crossborder</category>
      <category>fintech</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Afriex Webhook Integration Guide: Signature Verification, Event Handling, and Production Best Practices</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Mon, 25 May 2026 01:17:44 +0000</pubDate>
      <link>https://dev.to/afriex/afriex-webhook-integration-guide-signature-verification-event-handling-and-production-best-3c9o</link>
      <guid>https://dev.to/afriex/afriex-webhook-integration-guide-signature-verification-event-handling-and-production-best-3c9o</guid>
      <description>&lt;p&gt;When you create a transaction through the Afriex Business API, the response you get back is just the start. The transaction comes back with a status of &lt;code&gt;PENDING&lt;/code&gt;. What happens after that — whether it moves to &lt;code&gt;PROCESSING&lt;/code&gt;, &lt;code&gt;COMPLETED&lt;/code&gt;, &lt;code&gt;IN_REVIEW&lt;/code&gt;, or &lt;code&gt;FAILED&lt;/code&gt; arrives through webhooks.&lt;/p&gt;

&lt;p&gt;Most integration bugs in payment systems trace back to webhook handling, not the API calls themselves. Missed signature verification. Handlers that time out. Status updates applied twice. Fields read from parsed JSON instead of the raw body. These are the mistakes that cause payouts to look settled when they are not, or trigger duplicate notifications to your users.&lt;/p&gt;

&lt;p&gt;This article covers how Afriex webhooks work, every event the system fires, how to verify signatures correctly, how to build a handler that holds up in production, and how to test locally before you go live.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Afriex sends and when
&lt;/h2&gt;

&lt;p&gt;Afriex fires a signed HTTP POST to your configured webhook URL whenever a resource changes. Three resource types generate events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction events&lt;/strong&gt; are the ones you will interact with most. Every time a transaction is created or its status changes, Afriex fires either &lt;code&gt;TRANSACTION.CREATED&lt;/code&gt; or &lt;code&gt;TRANSACTION.UPDATED&lt;/code&gt;. Here's the full status vocabulary that a transaction moves through:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PENDING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transaction received, waiting to be processed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROCESSING&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Actively being processed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COMPLETED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Settled successfully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SUCCESS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alias for a settled transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FAILED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Failed. Check &lt;code&gt;meta&lt;/code&gt; for details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CANCELLED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cancelled before processing started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REFUNDED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Funds returned to sender&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IN_REVIEW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Under manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REJECTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejected after review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RETRY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Being automatically retried by the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UNKNOWN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Status could not be determined. Contact support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Customer events&lt;/strong&gt; fire when a customer is created (&lt;code&gt;CUSTOMER.CREATED&lt;/code&gt;), their details are updated (&lt;code&gt;CUSTOMER.UPDATED&lt;/code&gt;), or they are deleted (&lt;code&gt;CUSTOMER.DELETED&lt;/code&gt;). These are useful for keeping your local customer records in sync with Afriex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment method events&lt;/strong&gt; fire on creation (&lt;code&gt;PAYMENT_METHOD.CREATED&lt;/code&gt;), update (&lt;code&gt;PAYMENT_METHOD.UPDATED&lt;/code&gt;), and deletion (&lt;code&gt;PAYMENT_METHOD.DELETED&lt;/code&gt;). If a payment method is deleted on the Afriex side, your application needs to know so it can prompt the user to attach a new one before the next payout.&lt;/p&gt;

&lt;p&gt;There is also &lt;code&gt;CHECKOUT_SESSION.CREATED&lt;/code&gt; for checkout flows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before you go live: allowlist the IP addresses
&lt;/h2&gt;

&lt;p&gt;This step catches developers off guard. Before Afriex can deliver webhooks to your server, your firewall must allow inbound traffic from Afriex's IP addresses. Webhook requests from any other IP should be blocked regardless of whether the signature is valid.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;IP Address&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox&lt;/td&gt;
&lt;td&gt;&lt;code&gt;34.234.189.210&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;code&gt;34.197.33.100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Add these to your firewall or security group allowlist. Without this, Afriex webhook requests will be silently blocked before they reach your handler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting up your endpoint
&lt;/h2&gt;

&lt;p&gt;In your &lt;a href="https://business.afriex.com/" rel="noopener noreferrer"&gt;Afriex dashboard&lt;/a&gt;, go to &lt;strong&gt;Developers&lt;/strong&gt; then the &lt;strong&gt;Webhooks&lt;/strong&gt; tab. Paste your endpoint URL and save. Your webhook public key is on the same screen — copy it and store it as an environment variable. You will need it for signature verification.&lt;/p&gt;

&lt;p&gt;Staging and production use different public keys. Make sure you are using the correct one for each environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Signature verification
&lt;/h2&gt;

&lt;p&gt;Every webhook Afriex sends includes an &lt;code&gt;x-webhook-signature&lt;/code&gt; header. This is a Base64-encoded RSA-SHA256 signature of the raw request body, signed with Afriex's private key. You verify it using the public key from your dashboard.&lt;/p&gt;

&lt;p&gt;Two things to get right here that developers frequently get wrong:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify against the raw body, not parsed JSON.&lt;/strong&gt; If you parse the body to JSON first and then try to verify the signature against the re-serialized string, it will fail. The signature was computed against the exact bytes Afriex sent. Any transformation — even a whitespace difference — breaks it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reject the request immediately if verification fails.&lt;/strong&gt; Do not process the payload. Do not log it as a real event. Return &lt;code&gt;400&lt;/code&gt; or &lt;code&gt;401&lt;/code&gt; and stop.&lt;/p&gt;

&lt;p&gt;Here is the correct verification implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVerify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RSA-SHA256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a Next.js API route, you need to read the raw body before any parsing happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/webhooks/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Read raw body before any parsing&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// handle payload&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a Fastify application, you need to preserve the raw body before Fastify's JSON parser consumes it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Register this plugin before routes&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContentTypeParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;parseAs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Attach raw string to request for webhook verification&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The payload structure
&lt;/h2&gt;

&lt;p&gt;Every Afriex webhook follows the same envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRANSACTION.UPDATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;event&lt;/code&gt; field tells you what happened. The &lt;code&gt;data&lt;/code&gt; field contains the resource. Here is what each resource type looks like:&lt;/p&gt;

&lt;h3&gt;
  
  
  Transaction payload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRANSACTION.UPDATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"transactionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"69d60071ab82306f11b03393"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WITHDRAW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.28847"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sourceCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"destinationAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"destinationCurrency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"destinationId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"690df3281c11eea59108fcaf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"69528240ba52c13b669fb239"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ref-withdraw-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"idempotencyKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"idem-withdraw-001"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-08T07:14:57.444Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updatedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-08T07:15:30.000Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Customer payload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CUSTOMER.UPDATED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"698b0440cba7ec3daee9163d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"johnsmith@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+2348012345678"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"countryCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-10T10:11:12.415Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updatedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-11T15:30:45.123Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Payment method payload
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PAYMENT_METHOD.DELETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paymentMethodId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"69f87b0dcc0ee96511560796"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BANK_ACCOUNT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6922e4520a53e858ab42efa8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"institution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"institutionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"058"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GTBank"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"accountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"countryCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NG"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Building a production-grade handler
&lt;/h2&gt;

&lt;p&gt;A webhook handler has one job: acknowledge receipt fast, then process asynchronously. Afriex expects a &lt;code&gt;2xx&lt;/code&gt; response within about 5 seconds. If your handler does database writes, sends emails, or calls other APIs synchronously before returning, you will hit that window under any real load.&lt;/p&gt;

&lt;p&gt;The pattern that holds up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify signature&lt;/li&gt;
&lt;li&gt;Return &lt;code&gt;200&lt;/code&gt; immediately&lt;/li&gt;
&lt;li&gt;Push the raw payload to a queue&lt;/li&gt;
&lt;li&gt;Process in a background worker
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lean handler — verify, acknowledge, enqueue&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyWebhookSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Enqueue for async processing — do not process inline&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handle each event correctly
&lt;/h3&gt;

&lt;p&gt;Not every status requires the same response. Here is a decision map for transaction events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleTransactionEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Update your database first&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateTransactionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PROCESSING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Informational — no user-facing action needed&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Terminal success — notify user, update UI state&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendPayoutConfirmationEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IN_REVIEW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Compliance hold — notify user that payout is under review&lt;/span&gt;
      &lt;span class="c1"&gt;// Do not mark as failed. Wait for further updates.&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifyPayoutUnderReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RETRY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Network is retrying automatically — no action needed&lt;/span&gt;
      &lt;span class="c1"&gt;// Do not alarm the user&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Terminal failure — notify user, allow them to retry&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendPayoutFailedAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;// Indeterminate — log and alert your team to investigate&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;alertTeam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Transaction &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; reached UNKNOWN status`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;IN_REVIEW&lt;/code&gt; and &lt;code&gt;RETRY&lt;/code&gt; statuses are the ones most developers handle incorrectly. &lt;code&gt;IN_REVIEW&lt;/code&gt; is not a failure — it is a compliance hold that will resolve into &lt;code&gt;COMPLETED&lt;/code&gt; or &lt;code&gt;REJECTED&lt;/code&gt;. If you mark it as failed and notify the user, you will have unhappy users chasing payouts that are actually in progress. &lt;code&gt;RETRY&lt;/code&gt; means the network is handling it automatically. No action needed on your end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make your handler idempotent
&lt;/h3&gt;

&lt;p&gt;Afriex retries webhook delivery up to 12 times with exponential backoff. Your handler will receive the same event more than once. That is by design. Your code needs to handle it gracefully.&lt;/p&gt;

&lt;p&gt;The simplest approach: store a record of processed webhook event IDs and skip any you have already handled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processWebhookEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if we have already processed this exact event&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alreadyProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processedWebhooks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processedWebhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alreadyProcessed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Already handled — acknowledge and return&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Process the event&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleTransactionEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Mark as processed&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processedWebhooks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;processedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the idempotency key you can use a combination of &lt;code&gt;event&lt;/code&gt; type, resource ID, and &lt;code&gt;updatedAt&lt;/code&gt; timestamp. This way, the same status update arriving twice is treated as a duplicate and skipped, but a genuine status change on the same transaction (e.g., &lt;code&gt;PROCESSING&lt;/code&gt; followed by &lt;code&gt;COMPLETED&lt;/code&gt;) is treated as two distinct events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle customer and payment method events
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleCustomerEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomerWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CUSTOMER.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Keep your local record in sync&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateLocalCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CUSTOMER.DELETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Mark the customer as removed in your DB&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markCustomerDeleted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePaymentMethodEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaymentMethodWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PAYMENT_METHOD.DELETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Remove from your DB and flag the customer as needing a new payout method&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;removePaymentMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;flagCustomerNeedsPaymentMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Retry behavior
&lt;/h2&gt;

&lt;p&gt;Afriex retries failed webhook deliveries up to 12 times. The schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30s → 1m → 2m → 4m → 8m → 16m → 32m → 1h → 2h → 4h → 8h → 16h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A delivery is considered failed if your endpoint returns a non-2xx status or does not respond within about 5 seconds. This means your handler timing out is treated the same as a hard error — Afriex will retry.&lt;/p&gt;

&lt;p&gt;Two practical implications. First, your handler must respond quickly (within 5 seconds) regardless of what processing needs to happen — hence the enqueue-and-return pattern above. Second, you should never rely solely on webhooks for reconciliation. Build a polling fallback: periodically call &lt;code&gt;GET /api/v1/transaction/:id&lt;/code&gt; for transactions that have been in a non-terminal status for longer than expected. Webhooks are the fast path. The API is the source of truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing locally
&lt;/h2&gt;

&lt;p&gt;Afriex provides a sandbox-only endpoint for firing real signed webhooks against your local handler without needing to manufacture underlying activity. You create an entity (a customer, payment method, or transaction) in sandbox, then call the trigger endpoint with the entity ID and the event name you want to test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://sandbox.api.afriex.com/api/v1/webhooks/trigger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'x-api-key: your-sandbox-api-key'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "event": "TRANSACTION.UPDATED",
    "entityId": "69d60071ab82306f11b03393"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afriex will send a real signed webhook to your configured callback URL using that entity as the payload. Because it is a real signed payload, your signature verification code runs exactly as it would in production.&lt;/p&gt;

&lt;p&gt;To receive it locally, expose your dev server with &lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the HTTPS URL ngrok gives you as your webhook URL in the Afriex sandbox dashboard, then fire the trigger. You can test every event type this way — &lt;code&gt;CUSTOMER.CREATED&lt;/code&gt;, &lt;code&gt;PAYMENT_METHOD.DELETED&lt;/code&gt;, &lt;code&gt;TRANSACTION.UPDATED&lt;/code&gt; with any status — against a real entity in sandbox.&lt;/p&gt;

&lt;p&gt;The trigger endpoint returns &lt;code&gt;403 Forbidden&lt;/code&gt; in production, so there is no risk of accidentally firing test webhooks against your live environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checklist before going live
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Afriex IP addresses added to your server allowlist (&lt;code&gt;34.197.33.100&lt;/code&gt; for production)&lt;/li&gt;
&lt;li&gt;[ ] Webhook public key stored as an environment variable, not hardcoded&lt;/li&gt;
&lt;li&gt;[ ] Signature verification runs against raw body bytes, not parsed JSON&lt;/li&gt;
&lt;li&gt;[ ] Handler returns &lt;code&gt;2xx&lt;/code&gt; within 5 seconds&lt;/li&gt;
&lt;li&gt;[ ] Processing happens asynchronously after acknowledgement&lt;/li&gt;
&lt;li&gt;[ ] All 11 transaction statuses handled explicitly — including &lt;code&gt;IN_REVIEW&lt;/code&gt;, &lt;code&gt;RETRY&lt;/code&gt;, and &lt;code&gt;UNKNOWN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Handler is idempotent — safe to receive the same event multiple times&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;PAYMENT_METHOD.DELETED&lt;/code&gt; event triggers a flag in your database&lt;/li&gt;
&lt;li&gt;[ ] Polling fallback implemented for transactions stuck in non-terminal status&lt;/li&gt;
&lt;li&gt;[ ] Tested all event types using the sandbox trigger endpoint&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The full webhook reference is at &lt;a href="https://docs.afriex.com/api-reference/endpoint/webhooks/introduction" rel="noopener noreferrer"&gt;docs.afriex.com/api-reference/endpoint/webhooks/introduction&lt;/a&gt;. The transaction API reference, including the full request and response schema, is at &lt;a href="https://docs.afriex.com/api-reference/endpoint/transactions/create" rel="noopener noreferrer"&gt;docs.afriex.com/api-reference/endpoint/transactions/create&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>devex</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Agentic AI Is Changing Cross-Border Payments (and What It Means for Developers)</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Sun, 24 May 2026 06:21:36 +0000</pubDate>
      <link>https://dev.to/afriex/how-agentic-ai-is-changing-cross-border-payments-and-what-it-means-for-developers-3m12</link>
      <guid>https://dev.to/afriex/how-agentic-ai-is-changing-cross-border-payments-and-what-it-means-for-developers-3m12</guid>
      <description>&lt;p&gt;For most of the last decade, AI in payments meant one thing: fraud detection. A model sitting downstream, flagging suspicious transactions after the fact. Useful, but passive. The system still required a human or deterministic code to decide what to do next.&lt;/p&gt;

&lt;p&gt;That is changing fast. &lt;a href="https://thepaypers.com/payments/expert-views/2025-payments-retrospective-the-reshaping-forces-of-agentic-ai-a2a-rtps-and-cbdcs" rel="noopener noreferrer"&gt;Agentic AI emerged as the breakout technology of 2025, moving from demos into regulated payment workflows&lt;/a&gt;. The difference is not the model. It is the architecture. Agentic systems do not just classify or predict. They plan, execute multi-step workflows, and take action across external systems without a human in the loop for every step. In payments, that shift has real consequences for how infrastructure gets built and what developers need to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  What agentic AI actually means in a payments context
&lt;/h2&gt;

&lt;p&gt;The term gets used loosely, so a working definition is worth establishing. An AI agent in a payment context is a system that receives a high-level goal, decides what sequence of API calls to make to achieve it, executes them, handles failures, and reports the outcome, with no human approving each individual step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.elibrary.imf.org/view/journals/068/2026/004/article-A001-en.xml" rel="noopener noreferrer"&gt;The IMF's May 2026 note on agentic AI in payments&lt;/a&gt; describes the scope of experimentation as expanding rapidly: from fraud detection and compliance monitoring to treasury optimization and cross-border payment orchestration. &lt;a href="https://www.fenwick.com/insights/publications/is-2026-the-year-of-agentic-payments" rel="noopener noreferrer"&gt;Fenwick's 2026 agentic payments analysis&lt;/a&gt; draws the line clearly: unlike traditional autopay automation, agentic AI makes decisions and takes actions to achieve goals. It is not executing a predefined script.&lt;/p&gt;

&lt;p&gt;The practical difference for a developer is this: instead of writing code that calls &lt;code&gt;get_balance&lt;/code&gt;, evaluates the result, and conditionally calls &lt;code&gt;create_transaction&lt;/code&gt;, you expose those capabilities as tools and let the model decide the sequence based on a stated goal. The orchestration logic moves from your codebase to the model. The code you write shrinks. The capability surface expands.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where agentic payment automation is already happening
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.fxcintel.com/research/analysis/ai-earnings-mentions-2025" rel="noopener noreferrer"&gt;A March 2026 analysis of earnings calls across 24 companies in the cross-border payments space&lt;/a&gt; found AI mentions surging significantly year over year. The themes were not hypothetical. NatWest said AI agents can "execute complex banking workflows" on behalf of customers. Remitly announced plans to deploy agentic technology across productivity, fraud reduction, and decision-making in 2026.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techinformed.com/agentic-ai-and-more-to-reshape-fintech-in-2026/" rel="noopener noreferrer"&gt;Agentic AI is moving toward anticipating intent, verifying identity, detecting fraud, and authorizing transactions in real time across platforms&lt;/a&gt;, all in a single autonomous workflow rather than across separate systems. Compliance teams are using agentic AI to shift from static rule-based watchlist screening to continuous, trigger-based monitoring. &lt;a href="https://substack.com/@samboboev/note/c-202832396" rel="noopener noreferrer"&gt;Watchlist screening currently generates 90 to 95 percent false positives&lt;/a&gt;, and agentic systems with richer contextual reasoning are actively pushing that number down.&lt;/p&gt;

&lt;p&gt;For cross-border payment infrastructure specifically, the primary application is treasury optimization and payment orchestration. &lt;a href="https://substack.com/@samboboev/note/c-202832396" rel="noopener noreferrer"&gt;Cross-border flows hit $190 trillion annually&lt;/a&gt; and legacy systems still route most of that through multi-step correspondent banking chains. Agentic payment systems can evaluate multiple rail options in real time, select the optimal one for a given corridor and amount, monitor for settlement status, and escalate to a human only when a transaction falls outside expected parameters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architectural challenge this creates
&lt;/h2&gt;

&lt;p&gt;The IMF note identifies what it calls a central architectural challenge: as AI agents gain the ability to initiate and execute payment transactions autonomously, the traditional assumption that a human authorizes each individual transaction breaks down. The legal and liability frameworks built around that assumption do not map cleanly onto autonomous agent behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.fenwick.com/insights/publications/is-2026-the-year-of-agentic-payments" rel="noopener noreferrer"&gt;Existing financial and consumer protection laws built around human-decisioned transactions may not appropriately address the challenges raised by agentic payments&lt;/a&gt;. Companies building agentic payment automation need to navigate unsettled questions under AI laws, money transmitter regimes, and authorization frameworks simultaneously.&lt;/p&gt;

&lt;p&gt;For developers, this creates two concrete requirements. First, every action an agent takes on behalf of a user must be logged with enough detail to reconstruct exactly what the agent decided, what information it had at the time, and what it executed. Immutable audit logs are not a nice-to-have in agentic payment systems. They are the primary mechanism for accountability. Second, agent scope must be explicitly bounded. An agent authorized to send payroll disbursements should not be able to initiate arbitrary transactions outside that context. Tool-level permission scoping, not just API key permissions, is the right model here.&lt;/p&gt;




&lt;h2&gt;
  
  
  What agentic orchestration requires from payment APIs
&lt;/h2&gt;

&lt;p&gt;The shift toward autonomous payment systems changes the requirements for the payment APIs agents build on. These requirements are stricter than what human-driven integrations demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool descriptions carry the same weight as endpoint documentation.&lt;/strong&gt; When a human developer integrates an API, they read the docs and write code. When an AI agent integrates a payment API, it reads the tool descriptions and decides what to call. An ambiguous tool description produces wrong agent behavior the same way an ambiguous endpoint contract produces bugs. Payment infrastructure providers who want their APIs used in agentic payment workflows need to treat tool descriptions as a first-class product concern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error responses need to be machine-interpretable, not just human-readable.&lt;/strong&gt; An agent that receives "Something went wrong" cannot decide whether to retry, escalate, or abort. Structured error codes like &lt;code&gt;INSUFFICIENT_BALANCE&lt;/code&gt;, &lt;code&gt;FX_RATE_EXPIRED&lt;/code&gt;, and &lt;code&gt;PAYMENT_METHOD_INVALID&lt;/code&gt; give the model the information it needs to make the right decision without human intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction status granularity matters more than it did before.&lt;/strong&gt; In a human-driven integration, a developer can decide how to handle an ambiguous status. In an agentic payment workflow, the agent needs enough signal to act correctly on its own. A payment API that returns &lt;code&gt;PENDING&lt;/code&gt; for multiple distinct states forces the agent to guess. A well-designed one surfaces the full status vocabulary the underlying network produces: &lt;code&gt;PENDING&lt;/code&gt;, &lt;code&gt;PROCESSING&lt;/code&gt;, &lt;code&gt;IN_REVIEW&lt;/code&gt;, &lt;code&gt;COMPLETED&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt;, &lt;code&gt;REJECTED&lt;/code&gt;, &lt;code&gt;RETRY&lt;/code&gt;, &lt;code&gt;REFUNDED&lt;/code&gt;. Each status maps to a different agent decision. &lt;code&gt;IN_REVIEW&lt;/code&gt; means wait and poll. &lt;code&gt;RETRY&lt;/code&gt; means the network is handling it. &lt;code&gt;REJECTED&lt;/code&gt; means surface to a human. The Afriex Business API exposes exactly this vocabulary on every transaction, which is one reason it is well-suited as infrastructure for agentic payment automation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency is non-negotiable.&lt;/strong&gt; Agents retry. Networks fail. &lt;a href="https://medium.com/@jyc.dev/idempotency-in-software-engineering-why-it-matters-and-how-to-implement-it-2025-guide-c1ef8ad21965" rel="noopener noreferrer"&gt;Without idempotency keys, payment APIs risk duplicate transactions that are costly and difficult to reverse&lt;/a&gt;. An autonomous payment system without idempotent operations is a double-payment incident waiting to happen. The Afriex SDK accepts an idempotency key on every transaction creation call, which means retrying a failed disbursement job is safe by design.&lt;/p&gt;




&lt;h2&gt;
  
  
  The African corridor context
&lt;/h2&gt;

&lt;p&gt;For developers building agentic payment automation for African markets, the infrastructure complexity is higher than most global payment APIs assume, and the choice of payment API matters more as a result.&lt;/p&gt;

&lt;p&gt;Mobile money is the dominant payment rail in East and West Africa, not cards and not bank transfers. FX volatility in corridors like NGN/USD means a rate that is valid at the moment an agent job is created may be meaningfully different at the moment of settlement. Transaction statuses like &lt;code&gt;IN_REVIEW&lt;/code&gt; reflect real compliance holds that African payment networks produce, not just generic processing delays. An agent operating in this environment needs access to tools that reflect those realities rather than abstracting them away.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://business.afriex.com/" rel="noopener noreferrer"&gt;Afriex Business API&lt;/a&gt; is built against this infrastructure directly. Mobile money, bank transfers, SWIFT, and local payment channels are all first-class integrations. Exchange rates are live across NGN, KES, GHS, GBP, and other African corridor pairs. The &lt;a href="https://docs.afriex.com/mcp/introduction" rel="noopener noreferrer"&gt;Afriex MCP server&lt;/a&gt; exposes the full API surface as 22 callable tools for agentic workflows: &lt;code&gt;get_rates&lt;/code&gt; for live rates before committing to a disbursement, &lt;code&gt;get_balance&lt;/code&gt; to verify funds before a payroll run starts, &lt;code&gt;resolve_payment_method&lt;/code&gt; to verify a recipient account before attaching it, &lt;code&gt;create_transaction&lt;/code&gt; with idempotency key support, and &lt;code&gt;get_transaction&lt;/code&gt; for status polling after execution. The full transaction status vocabulary, including &lt;code&gt;IN_REVIEW&lt;/code&gt;, &lt;code&gt;RETRY&lt;/code&gt;, and &lt;code&gt;REJECTED&lt;/code&gt;, is surfaced through webhooks so an agent always has enough signal to decide its next action correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to build now versus what to watch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build now: agentic payroll disbursement.&lt;/strong&gt; The use case is clear, the failure modes are well-understood, and the liability surface is bounded because a human approves the payroll run before the agent executes it. The agent's autonomy is scoped to execution, not authorization. This is the lowest-risk entry point for agentic payment automation and has the most direct ROI. The architecture for this, including how it integrates with the Afriex Business API, is covered in the &lt;a href="https://dev.to/afriex"&gt;companion architecture document&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build now: agentic FX monitoring and settlement timing.&lt;/strong&gt; An agent that watches a currency corridor, evaluates whether the current rate is within a threshold, and triggers settlement when conditions are met is straightforward to implement using the Afriex MCP server's &lt;code&gt;get_rates&lt;/code&gt; tool on a schedule. It is immediately valuable for any business running frequent cross-border payment flows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch: fully autonomous payment authorization.&lt;/strong&gt; Agents that can initiate arbitrary payments based on their own assessment of conditions, without a human approving each batch, sit in genuinely unsettled legal territory. &lt;a href="https://www.fenwick.com/insights/publications/is-2026-the-year-of-agentic-payments" rel="noopener noreferrer"&gt;The legal framework for autonomous payment authorization is unresolved&lt;/a&gt;, and building ahead of regulatory clarity is a risk most developers should not take on without specific legal guidance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch: multi-agent payment orchestration.&lt;/strong&gt; Chains of agents handing off payment decisions to each other across organizational boundaries are one of the most technically and legally complex areas in agentic AI right now. &lt;a href="https://www.elibrary.imf.org/view/journals/068/2026/004/article-A001-en.xml" rel="noopener noreferrer"&gt;The IMF note&lt;/a&gt; identifies this as a central challenge. The infrastructure protocols for secure multi-agent communication across payment networks are still being standardized.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Agentic AI does not change what cross-border payment infrastructure needs to do. It changes who is doing the orchestrating. The requirements for the underlying payment API layer get stricter as a result: machine-interpretable error codes, granular transaction status vocabularies, idempotent operations, and tool descriptions precise enough for a model to act on correctly without human clarification.&lt;/p&gt;

&lt;p&gt;For developers, the opportunity in agentic payment automation is real and immediate in bounded use cases. The risk is real too, and proportional to how much autonomous authorization you give the agent. Start with execution autonomy, keep authorization human, and build the audit trail as if regulators will read it. Because eventually, they will.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fintech</category>
      <category>afriex</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
