<?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: Rapyd</title>
    <description>The latest articles on DEV Community by Rapyd (@rapyd).</description>
    <link>https://dev.to/rapyd</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.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4474%2Fcae7f36b-e8a7-4141-b3b2-becfa1147ff9.png</url>
      <title>DEV Community: Rapyd</title>
      <link>https://dev.to/rapyd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rapyd"/>
    <language>en</language>
    <item>
      <title>AI for Regulatory Compliance in Payments</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Wed, 11 Mar 2026 18:30:37 +0000</pubDate>
      <link>https://dev.to/rapyd/ai-for-regulatory-compliance-in-payments-11g9</link>
      <guid>https://dev.to/rapyd/ai-for-regulatory-compliance-in-payments-11g9</guid>
      <description>&lt;p&gt;By: &lt;strong&gt;Manish Hatwalne&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rapyd Protect is an AI-powered fraud detection service built into Rapyd's payment platform. It monitors transactions in real time across bank transfers, cards, and e-wallets, using machine learning (ML) models to identify suspicious patterns and potential compliance violations. &lt;/p&gt;

&lt;p&gt;For developers building payment applications, regulatory compliance requirements like Anti-Money Laundering (AML) monitoring, suspicious activity reporting, and transaction screening can be difficult to implement. Rapyd Protect addresses these challenges through automated checks, ML-based risk scoring, and configurable review workflows. When a transaction triggers compliance (or fraud) concerns, the system automatically quarantines it and notifies your application via webhook, helping you maintain audit trails for regulatory purposes.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to use Rapyd Protect's fraud rules for compliance monitoring. You'll see how ML flags high-risk transactions for you review, and how to implement review workflows that meet regulatory requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rapyd Protect and Regulatory Compliance
&lt;/h2&gt;

&lt;p&gt;Payment systems operate under multiple regulatory frameworks designed to prevent financial crime and protect consumers. &lt;a href="https://www.rapyd.net/blog/aml-kyc-differences/#:~:text=AML%20Requirements" rel="noopener noreferrer"&gt;AML regulations&lt;/a&gt; require monitoring for suspicious transaction patterns that could indicate money laundering or terrorist financing. Know Your Customer (KYC) and &lt;a href="https://docs.rapyd.net/en/activating-your-account--kyb-.html" rel="noopener noreferrer"&gt;Know Your Business (KYB)&lt;/a&gt; rules mandate identity verification for individuals and companies conducting transactions. The &lt;a href="https://www.rapyd.net/blog/what-businesses-need-to-know-about-psd2/" rel="noopener noreferrer"&gt;Payment Services Directive 2 (PSD2)&lt;/a&gt; in Europe enforces Strong Customer Authentication and secure communication standards, while the &lt;a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation" rel="noopener noreferrer"&gt;General Data Protection Regulation (GDPR)&lt;/a&gt; governs how personal data is collected and processed. Regional frameworks like the &lt;a href="https://www.oag.ca.gov/privacy/ccpa" rel="noopener noreferrer"&gt;California Consumer Privacy Act (CCPA)&lt;/a&gt; and Brazil's &lt;a href="https://www.gov.br/esporte/pt-br/acesso-a-informacao/lgpd" rel="noopener noreferrer"&gt;Lei Geral de Proteção de Dados (LGPD)&lt;/a&gt; add additional layers of compliance requirements based on geographic location.&lt;/p&gt;

&lt;p&gt;Rapyd Protect handles these compliance challenges using ML models that analyze transaction data in real time by evaluating each transaction against patterns learned from Rapyd's global payment network, identifying anomalies that may indicate compliance violations. These models consider multiple factors including transaction velocity, geographic origin, payment amounts, and behavioral patterns. Based on this analysis, the system assigns risk scores to each transaction. When a transaction exceeds acceptable risk thresholds or matches suspicious activity patterns, Rapyd Protect automatically halts it and places it in quarantine for review.&lt;/p&gt;

&lt;p&gt;This approach detects compliance issues that static rules alone might miss. The system adapts to emerging fraud trends and regulatory risks as they develop, giving you continuous protection without constant manual updates.&lt;/p&gt;

&lt;p&gt;When Rapyd Protect quarantines a transaction, it communicates the status through a series of &lt;a href="https://docs.rapyd.net/en/quarantine-webhooks.html" rel="noopener noreferrer"&gt;webhooks&lt;/a&gt; sent to your application. These webhooks provide the detailed information you need to maintain compliance audit trails, update transaction statuses, and communicate with customers about delayed payments. &lt;/p&gt;

&lt;h3&gt;
  
  
  How Machine Learning Benefits Compliance Monitoring
&lt;/h3&gt;

&lt;p&gt;Rapyd Protect's machine learning capabilities handle a challenge that's impossible to solve manually: analyzing &lt;em&gt;massive&lt;/em&gt; amounts of transaction data in real time spotting suspicious patterns that human compliance officers would miss.&lt;/p&gt;

&lt;p&gt;The Rapyd Protect ML system runs 24/7, analyzing transaction ledgers even when your compliance teams are offline. This constant surveillance is critical for meeting regulatory requirements. Each transaction creates a ledger entry with detailed information like the payment amount, parties involved, geographic locations, timestamps, and payment methods. The ML models scan these ledgers for discrepancies and anomalies, comparing new transactions against historical patterns to catch deviations that might signal compliance violations.&lt;/p&gt;

&lt;p&gt;A major advantage of Rapyd's machine learning approach is its ability to spot subtle patterns in vast datasets.The system can detect small changes in transaction timing, gradual increases in payment amounts, or unusual connections between seemingly unrelated accounts. These patterns often indicate attempts to evade detection. Activities that look normal individually may reveal suspicious intent when analyzed collectively. The &lt;a href="https://docs.rapyd.net/en/rapyd-protect-overview.html#:~:text=Velocity%20Engine" rel="noopener noreferrer"&gt;Velocity Engine&lt;/a&gt; tracks purchase frequency and usage patterns across multiple timeframes, flagging sudden spikes in transaction velocity or suspicious pattern shifts that need investigation.&lt;/p&gt;

&lt;p&gt;When the ML models identify a potential compliance issue, they don't just flag the transaction. They also provide context that helps human reviewers make informed decisions. The system analyzes patterns across your entire payment network, correlating data from multiple sources to build a complete risk profile for each flagged transaction.&lt;/p&gt;

&lt;p&gt;This gives your compliance officers much greater reach. Each team member can effectively monitor far more transactions than manual review would allow. The system catches regulatory violations quickly, creating a stronger defense against financial crime. And because the ML models adapt to new threats automatically, your payment application stays compliant even as regulations and fraud tactics evolve. This means less ongoing maintenance work for your team.&lt;/p&gt;

&lt;p&gt;The diagram below illustrates how Rapyd Protect's machine learning engine evaluates each transaction and determines the appropriate compliance action:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Ff045k262tv2f0evhgjie.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.amazonaws.com%2Fuploads%2Farticles%2Ff045k262tv2f0evhgjie.png" alt="Rapyd Protect Transaction Review Process" width="594" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Compliance Review Workflows with Rapyd
&lt;/h3&gt;

&lt;p&gt;When Rapyd Protect identifies a transaction that requires investigation, it automatically halts the payment flow and places it in quarantine. This quarantine system serves as a critical checkpoint for regulatory compliance (or fraud detection), ensuring that potentially problematic transactions receive proper scrutiny before processing. It's built directly into Rapyd's payment infrastructure and communicates transaction status changes through a standardized webhook system (calling your predefined URL with a payload) that you can integrate into your application.&lt;/p&gt;

&lt;p&gt;Rapyd Protect uses four distinct &lt;a href="https://docs.rapyd.net/en/rapyd-protect.html" rel="noopener noreferrer"&gt;quarantine webhooks&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Quarantine Under Review&lt;/strong&gt; webhook triggers immediately when a transaction is placed in quarantine, providing your application with the transaction details and the reason for the hold. This notification allows you to update your system's transaction status, inform customers about the delay, and route the case to appropriate compliance personnel for investigation. The webhook payload includes essential information needed for compliance tracking and customer communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a compliance officer reviews the quarantined transaction, Rapyd Protect sends one of three resolution webhooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Quarantine Released&lt;/strong&gt; webhook indicates that the transaction has been approved and will proceed to completion. &lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Quarantine Declined&lt;/strong&gt; webhook signals that the transaction has been rejected due to compliance (or fraud) concerns and will not be processed. &lt;/li&gt;
&lt;li&gt;In cases where a transaction is approved but encounters technical issues during release, the &lt;strong&gt;Quarantine Release Failed&lt;/strong&gt; webhook notifies your application that manual intervention may be required to resolve the processing error.&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2Fsj81v2844wvyx5b7ja9o.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.amazonaws.com%2Fuploads%2Farticles%2Fsj81v2844wvyx5b7ja9o.png" alt="Rapyd Protect Webhooks" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The manual review process must take place within a &lt;strong&gt;7-day window&lt;/strong&gt;. During this time, compliance officers should examine quarantined transactions against regulatory requirements. They should evaluate customer transaction history, geographic risk indicators, and regulatory alignment before making approval or decline decisions. This timeframe balances thorough compliance scrutiny with maintaining reasonable payment processing speeds.&lt;/p&gt;

&lt;p&gt;For developers, integrating this workflow requires proper webhook handling within your application architecture. When receiving a Quarantine Under Review webhook, you should update the transaction status in your database, trigger customer notifications about the delay, and route case details to your compliance dashboard for tracking. The webhook payload contains identifiers that correlate the quarantined transaction with your original payment request, ensuring accurate status updates across your system.&lt;/p&gt;

&lt;p&gt;A typical webhook payload looks like this for a &lt;code&gt;QUARANTINE_UNDER_REVIEW&lt;/code&gt; event:&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;"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;"wh_540b8a22cd77283ec2a721362e4de32d"&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;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qm_a5169383ddd6f0e04f716601dbc7375e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"target_tokens"&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="s2"&gt;"payout_71987d8e65a4e7a68a5ea000e1984a24"&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;"limits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compliance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"compliance"&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;"HLD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1646532379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"error_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1646532379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action_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;"create_payout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"resolved_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"action_flow_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;"6a8840f9-5ebd-423c-8eab-b397b5ef81f1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"duplicated_action_flow_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;"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;"QUARANTINE_UNDER_REVIEW"&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;"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;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1646532379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"extended_timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1646532379934&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger_operation_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;"f0c9ecbf-a238-4fa9-b0d5-a00aa3e322e6"&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;These webhooks allow you to build compliant payment applications with proper audit trails. Each webhook provides timestamped records of compliance decisions, creating an immutable log of when transactions were flagged, who reviewed them, and what actions were taken. This documentation is essential for regulatory examinations. By storing webhook data, your application can maintain a complete compliance history that demonstrates due diligence in monitoring suspicious transactions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Did you know?&lt;/strong&gt; Rapyd Protect also allows developers to create custom fraud rules that complement ML-based detection, so you can configure business-specific compliance checks like geographic restrictions or transaction amount thresholds. For detailed guidance, see this article about &lt;a href="https://docs.rapyd.net/en/rapyd-protect-fraud-rules.html" rel="noopener noreferrer"&gt;Enhancing Payment Fraud Detection with Rapyd Protect&lt;/a&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Benefits Of Automated Compliance Monitoring With Rapyd Protect
&lt;/h3&gt;

&lt;p&gt;When you integrate Rapyd's payment APIs, compliance monitoring through machine learning and rule-based detection automatically applies to every transaction your application processes. This automation makes it possible to keep pace with evolving regulations and emerging fraud patterns. As regulatory bodies like &lt;a href="https://www.fincen.gov/" rel="noopener noreferrer"&gt;FinCEN&lt;/a&gt; update AML requirements or new compliance risks emerge, Rapyd updates its machine learning models and detection logic without requiring changes to your application code. Your payment system continues meeting current compliance standards through automatic model updates informed by Rapyd's global transaction network and regulatory monitoring.&lt;/p&gt;

&lt;p&gt;The webhook system makes integration easy. Instead of constantly polling for transaction statuses or building complex compliance logic into your application, you simply respond to webhook events when compliance attention is needed. For example, you can automatically trigger emails to specific compliance team members based on the type of webhook event received. This keeps your compliance monitoring separate from your core business logic. Rapyd's infrastructure handles the regulatory monitoring work while your application focuses on what matters most: payment processing and customer experience.&lt;/p&gt;

&lt;p&gt;The following screenshot shows &lt;strong&gt;Under Review&lt;/strong&gt; transactions detected by the Rapyd Protect system:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fe9iphhqwcpkcztb99erc.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.amazonaws.com%2Fuploads%2Farticles%2Fe9iphhqwcpkcztb99erc.png" alt="Rapyd Under Review Transactions" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every quarantined transaction, compliance decision, and status change generates logged records accessible through your Rapyd account. These automated logs provide the audit trails required during regulatory examinations, documenting your payment system's compliance monitoring activities without manual record-keeping. The logs capture timestamps, decision rationale, and transaction details that demonstrate adherence to AML regulations and other compliance frameworks.&lt;/p&gt;

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

&lt;p&gt;Rapyd Protect transforms regulatory compliance by combining machine learning risk assessment with configurable workflows and real-time webhooks. The platform helps you build payment applications that meet complex regulatory requirements without constant manual oversight. The system monitors transactions continuously, flags compliance issues automatically, and maintains the audit trails necessary for regulatory examinations.&lt;/p&gt;

&lt;p&gt;For developers, this means faster time to market and reduced compliance overhead. Enterprise-grade compliance monitoring is built directly into Rapyd's infrastructure with no additional integration required. As regulations evolve, Rapyd Protect adapts automatically, keeping your applications aligned with current standards.&lt;/p&gt;

&lt;p&gt;If you're looking to automate compliance monitoring in your payment application, sign up for the &lt;a href="https://dashboard.rapyd.net/sign-up" rel="noopener noreferrer"&gt;Rapyd trial account&lt;/a&gt; and start using Rapyd Protect to experience AI-powered regulatory compliance firsthand.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>developer</category>
      <category>ai</category>
      <category>fintech</category>
    </item>
    <item>
      <title>Boosting Authorization Rates: Partnering Strategies and Intelligent Retries</title>
      <dc:creator>Drew Harris</dc:creator>
      <pubDate>Tue, 09 Dec 2025 18:13:02 +0000</pubDate>
      <link>https://dev.to/rapyd/boosting-authorization-rates-partnering-strategies-and-intelligent-retries-5all</link>
      <guid>https://dev.to/rapyd/boosting-authorization-rates-partnering-strategies-and-intelligent-retries-5all</guid>
      <description>&lt;p&gt;By: Adeyinka Adegbenro&lt;/p&gt;

&lt;p&gt;Authorization rates measure the ratio of successful payments to total attempts. For instance, if a merchant processes one hundred payments and seventy of them are successful, the authorization rate is 70 percent. This metric directly impacts revenue, customer satisfaction, and operational efficiency. High authorization rates mean more revenue captured, while poor rates can signal system issues, like failed authentication, unreliable processors, or friction in the payment flow.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn about the different stages of a payment and where declines typically happen. You'll also learn about strategies that can help boost your approval rates, including smart retries using &lt;a href="https://docs.rapyd.net/en/merchant-advice-codes.html" rel="noopener noreferrer"&gt;merchant advice codes&lt;/a&gt;, implementing artificial intelligence (AI) optimizations, and working with unified platforms like &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the Payment Authorization Funnel
&lt;/h2&gt;

&lt;p&gt;Before you can boost authorization rates, you need to understand how money moves from the customer to the merchant. Each stage is a potential point of failure. Understanding the moving parts helps you anticipate the kinds of authorization errors that may take place at different stages and how to respond strategically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stages of an Online Payment
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fd7r0k96y5xrfknocbfvk.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.amazonaws.com%2Fuploads%2Farticles%2Fd7r0k96y5xrfknocbfvk.png" alt="Diagram of the stages of an online payment, courtesy of Adeyinka Adegbenro" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The lifecycle of an online payment begins when a customer submits their card details on the merchant's application. That triggers an authorization request that is securely transmitted through a payment gateway (API) to a payment processor. The payment gateway and payment processor are often the same provider. &lt;a href="https://www.rapyd.net/developers/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; provides both services through one unified solution.&lt;/p&gt;

&lt;p&gt;Next, the payment processor may initiate a 3-D Secure (3DS) flow if required. During this step, the issuer may perform additional checks, like one-time passwords (OTPs) or biometric prompts. Once authentication is complete, the authorization request is then routed through a card network, like &lt;a href="https://visa.com/" rel="noopener noreferrer"&gt;Visa&lt;/a&gt;, &lt;a href="https://www.vervecardinfo.com/" rel="noopener noreferrer"&gt;Verve&lt;/a&gt;, or &lt;a href="https://mastercard.com/" rel="noopener noreferrer"&gt;Mastercard&lt;/a&gt;. The network, in turn, forwards the authorization request to the customer's issuing bank. Based on various signals, such as available balance, fraud checks, and card validity, the issuing bank ultimately decides whether to receive or reject the payment. The decision travels back through the same route to the merchant, where the result is displayed to the customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Declines and Visibility into the Funnel
&lt;/h3&gt;

&lt;p&gt;Declines can happen at any stage in the payment process, from network outages and expired cards to incorrect PINs or CVVs, fraud alerts, or insufficient funds. However, not all declines are created equal.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.rapyd.net/blog/the-merchants-guide-to-credit-card-decline-codes/" rel="noopener noreferrer"&gt;soft decline&lt;/a&gt; is temporary (&lt;em&gt;eg&lt;/em&gt; network timeouts, insufficient funds) and may succeed on retry. A hard decline (&lt;em&gt;eg&lt;/em&gt; stolen card, invalid number, wrong PIN) usually can't be resolved on retry. Developers need visibility into this funnel to know when and how to respond.&lt;/p&gt;

&lt;p&gt;For example, if a large portion of subscription payments fail due to expired cards, a retry will not be effective, but a card updater or reminder system may. This insight helps developers spot decline patterns and come up with strategies to prevent losing customers you've already spent money acquiring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Smart Retry Logic with MACs
&lt;/h2&gt;

&lt;p&gt;Merchant Advice Codes (MAC) are response indicators issued by banks or card networks that provide specific guidance when a transaction is declined. These responses usually follow a failed payment authorization attempt. The code helps the merchant determine whether the decline is temporary, permanent, or correctable. It can indicate whether to retry the transaction, prompt the customer for more information, or stop further attempts. By adhering to these codes, merchants can avoid needless retries and prevent damaging their authorization rates.&lt;/p&gt;

&lt;p&gt;The following are some common MACs and their meaning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MAC&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;01&lt;/td&gt;
&lt;td&gt;Updated Information needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Retry in the next hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;Retry after ten days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;Card reported stolen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;Invalid PIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7903&lt;/td&gt;
&lt;td&gt;Do not retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7921&lt;/td&gt;
&lt;td&gt;Do not honor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The examples in this guide are primarily sourced from Mastercard's MAC since MACs were first introduced by Mastercard. It's important to understand the meaning of each code and how to respond effectively. For example, if the code indicates &lt;code&gt;Do not retry&lt;/code&gt;, attempting the transaction again may trigger fraud alerts, chargebacks, fines, blacklisting, and reputational damage. You may forfeit any products or services that have already been delivered to the customer.&lt;/p&gt;

&lt;p&gt;A code like &lt;code&gt;Retry in the next hour&lt;/code&gt; suggests that the issue is temporary (&lt;em&gt;eg&lt;/em&gt; the spending limit has been reached or the cardholder's bank is temporarily unavailable). Some codes may point to missing or invalid data (in cases of an expired card or missing CVV), and if stronger customer authentication is required, merchants can prompt users to reenter or update their payment details or even go through a 3DS check. For subscription-based businesses, a card updater service may help avoid some of these errors entirely by refreshing card information automatically.&lt;/p&gt;

&lt;p&gt;Retrying only when it is appropriate increases your success rate without triggering issuer penalties or risking chargebacks. MAC-aware retry logic helps maintain healthy issuer relationships, reduce failed attempts, and improve overall conversion rates.&lt;/p&gt;

&lt;p&gt;It's important to note that MACs are not standardized across all issuers or payment processors. Different card issuers may return different codes for similar situations or none at all. In some cases, the MAC hints only at the problem without giving the full picture. It may even advise a retry, but that doesn't guarantee a successful retry.&lt;/p&gt;

&lt;p&gt;MACs should be used as part of a broader diagnostic strategy that includes logging, analytics, and close coordination with your payment provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AI to Predict and Optimize Authorization Outcomes
&lt;/h2&gt;

&lt;p&gt;Like so many other areas, AI is revolutionizing payments by enabling faster, smarter decision-making. For instance, when it comes to payment optimization, AI can detect patterns in payment behavior and adapt in real time by analyzing historical transaction data to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify high-risk combinations that are likely to be declined&lt;/li&gt;
&lt;li&gt;Recommend the best time or method to retry a failed payment&lt;/li&gt;
&lt;li&gt;Predict fraud and proactively block transactions that are likely to be flagged and rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI models automate analysis and continuous learning, leading to the discovery of emerging patterns in your data. This helps improve authorization rates and payment efficiency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case: Training Models on Transaction Data
&lt;/h3&gt;

&lt;p&gt;A common method for training AI models to predict authorization outcomes involves using a combination of historical and real-time transaction metadata. Here are some useful features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction amount&lt;/li&gt;
&lt;li&gt;Card bank identification number (BIN)&lt;/li&gt;
&lt;li&gt;Geolocation&lt;/li&gt;
&lt;li&gt;Timestamp&lt;/li&gt;
&lt;li&gt;Network quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With enough data, you can train a model to predict success probability for each available payment route or method. This makes it possible to implement &lt;a href="https://arxiv.org/pdf/2111.00783" rel="noopener noreferrer"&gt;smart routing&lt;/a&gt;. For any incoming payment, the system calculates the route with the highest likelihood of approval and uses it. On failure, the system retries the transaction through the next most optimal route. This approach helps merchants increase authorization rates while reducing unnecessary retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a Data-Driven Approach Boosts Success Rates
&lt;/h3&gt;

&lt;p&gt;AI enables the following intelligent mechanisms within payment systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data enrichment&lt;/strong&gt; augments payment requests by adding personally identifiable information (PII) that issuers have historically valued in approval decisions (&lt;em&gt;eg&lt;/em&gt; device information, geolocation).  Issuers are more likely to make better approval decisions when they have more contextual information about the cardholder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic retry&lt;/strong&gt; automates retries based on an evidence-based understanding of MACs or with a different payment service provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart filtering&lt;/strong&gt; filters out avoidable declines (&lt;em&gt;eg&lt;/em&gt; expired cards, duplicate payment submissions, fraud risk payments, bot activity, brute-force attempts with card PINs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart routing&lt;/strong&gt; selects the most promising processor most likely to approve a transaction, based on historical approval patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insight generation&lt;/strong&gt; uncovers patterns and root causes for declines, helping teams prioritize improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While AI has the ability to transform fraud detection, it's not without its challenges. Building, training, updating, and fine-tuning these models is complex work that demands massive data sets and serious computing power. Get the balance wrong, and you either block good customers (false positives) or let fraudsters slip through (false negatives).&lt;/p&gt;

&lt;p&gt;Then there's the regulatory maze. Data protection laws like the &lt;a href="https://gdpr-info.eu/" rel="noopener noreferrer"&gt;General Data Protection Regulation (GDPR)&lt;/a&gt; put strict limits on how you store non-anonymized customer data. Models must be designed to comply with these regulations by anonymizing and encrypting personal data.&lt;/p&gt;

&lt;p&gt;However, here's the thing: despite these challenges, AI makes payment systems more resilient and efficient—it's worth the effort. Even simple rule-based models can help small teams get started. You don't need a data science team or deep machine learning (ML) infrastructure to benefit from intelligent logic. Small teams can still improve success rates by implementing smart rule-based decision engines that handle MACs, flag common fraud indicators, or set retry thresholds. Over time, these systems can evolve into more sophisticated ML pipelines as resources and data maturity grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partnering with a Unified Payments Platform
&lt;/h2&gt;

&lt;p&gt;A unified payments platform offers end-to-end payment services, like acquisition, issuance, payouts, and digital wallets under a single provider. Instead of relying on multiple services for each step of the payment funnel, businesses can streamline operations, reduce costs, and ship faster by working with a unified solution.&lt;/p&gt;

&lt;p&gt;One major advantage of unified payment platforms is full-stack orchestration. Since these platforms manage both the acquiring and issuing sides of the payment flow, they have more access to granular data, such as issuer response data and fraud signals. This gives them better intelligence about what issuer response codes actually mean and how to act on them.&lt;/p&gt;

&lt;p&gt;For instance, platforms that issue their own cards or wallets can facilitate internal transactions with less friction, leading to faster approvals, lower fees, and fewer declines. While merchants can't control which card the customers use, partnering with a unified platform significantly improves the likelihood of successful payment outcomes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; supports payments, payouts, wallets, and card issuing under one umbrella. Even without ML support, the detailed issuer responses and metadata returned by Rapyd can serve as input for custom retry logic or AI decision models. This allows teams to improve retry outcomes without manually aggregating insights from multiple payment service providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Project: Payment Retry Orchestrator with MAC Logic
&lt;/h2&gt;

&lt;p&gt;This section walks you through a simple Node.js project that simulates multiple payment attempts and failure scenarios using MACs to guide retry behavior. The logic randomly fails some transactions and returns a corresponding MAC, which the orchestrator uses to decide what to do next: retry, prompt for a new method, or stop.&lt;/p&gt;

&lt;p&gt;If you prefer local development, you can clone the sample project on &lt;a href="https://github.com/Rapyd-Samples/rapyd-payment-retry-orchestrator" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Rapyd-Samples/rapyd-payment-retry-orchestrator

&lt;span class="nb"&gt;cd &lt;/span&gt;payment-retry-orchestrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also run this project instantly in your browser via &lt;a href="https://stackblitz.com/~/github.com/AdeyinkaAdegbenro/payment-retry-orchestrator" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;, with no setup required.&lt;/p&gt;

&lt;p&gt;The main components include the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/strong&gt; is the entry point of the project. It runs a batch of transactions using &lt;code&gt;runBatch&lt;/code&gt; and handles each one using  &lt;code&gt;processTransaction&lt;/code&gt;, which simulates the payment, receives a MAC, and applies the appropriate retry logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;simulator.js&lt;/code&gt;&lt;/strong&gt; contains the &lt;code&gt;simulateTransaction&lt;/code&gt; function, which randomly decides whether a transaction succeeds or fails. On failure, it returns a randomly selected MAC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;macLogic.js&lt;/code&gt;&lt;/strong&gt; defines the function &lt;code&gt;interpretMAC&lt;/code&gt; to translate a MAC to an action. The &lt;code&gt;MAC_RULES&lt;/code&gt; object provides a sample set of MACs to action mappings (&lt;em&gt;eg&lt;/em&gt; retry, prompt, or halt).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dashboard.js&lt;/code&gt;&lt;/strong&gt; includes a &lt;code&gt;report&lt;/code&gt; function that summarizes the outcome of all transactions that have run. It tracks metrics like total attempts, retries triggered, user prompts, and halted transactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/strong&gt; provides setup instructions and ideas for extending the project with ML-powered predictions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To run the simulator, in either your terminal or the StackBlitz terminal, execute the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the logs of each declined transaction and their corresponding meaning. After all the transactions have run, you'll see a summary report of all transactions:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fpbtsv576myo060l5kfky.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.amazonaws.com%2Fuploads%2Farticles%2Fpbtsv576myo060l5kfky.png" alt="Screenshot of the terminal output" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal isn't to replicate a full production-grade payment system. But as a foundation, you can extend with more advanced logic with payment orchestrator platforms like Rapyd, or even with predictive AI models.&lt;/p&gt;

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

&lt;p&gt;Declined payments are more than just lost revenue; they are pointers. By interpreting them and understanding the authorization funnel, developers can turn failed transactions into opportunities for recovery.&lt;/p&gt;

&lt;p&gt;With the right tools, you can go further than reactive retries. Rule-based retry strategies, AI-driven optimizations, and unified payment orchestration platforms like &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; can help you make smarter decisions at scale. Developers who take decline codes seriously as data points, rather than as dead ends, build smarter, anti-fragile systems that improve success rates and customer satisfaction.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>performance</category>
      <category>systemdesign</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mastering 3DS: Balancing Security, UX, and Authentication Rates</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Tue, 11 Nov 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/rapyd/mastering-3ds-balancing-security-ux-and-authentication-rates-19pc</link>
      <guid>https://dev.to/rapyd/mastering-3ds-balancing-security-ux-and-authentication-rates-19pc</guid>
      <description>&lt;p&gt;By: &lt;strong&gt;Adeyinka Adegbenro&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;3-D Secure (3DS) is an authentication protocol that adds an extra layer of security for card payments. It helps verify that the legitimate owner of a card is the one making the purchase. It also often serves as a mechanism to fulfill &lt;a href="https://www.mastercard.com/gateway/payment-solutions/secure-payments/strong-customer-authentication.html" rel="noopener noreferrer"&gt;Strong Customer Authentication (SCA)&lt;/a&gt;, particularly in regions subject to regulations like the &lt;a href="https://www.ecb.europa.eu/press/intro/mip-online/2018/html/1803_revisedpsd.en.html" rel="noopener noreferrer"&gt;revised Payment Services Directive (PSD2)&lt;/a&gt;, which mandates at least two forms of customer authentication.&lt;/p&gt;

&lt;p&gt;While 3DS facilitates two-factor authentication for online payments, it's designed to assess risk. This means not every payment requires an explicit challenge. Low-risk transactions can be completed through a frictionless flow without extra customer input. The protocol is meant to gauge if further security is needed for a specific transaction, and if so, the customer may be asked to input a PIN or a one-time password (OTP) sent to their device.&lt;/p&gt;

&lt;p&gt;The challenge when integrating 3DS into a payment flow is creating a balance between security, user experience, and high authorization rates. While 3DS can enhance security, it can also introduce friction that leads to user abandonment and lower approval rates if not implemented carefully.&lt;/p&gt;

&lt;p&gt;In this article, you'll go through the process of implementing 3DS and learn how to strike the balance between security, user experience, and authentication rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the 3DS Flow: Frictionless vs. Challenge
&lt;/h2&gt;

&lt;p&gt;The original 3DS protocol, 3-D Secure 1 (3DS1), was designed to protect merchants from fraudulent chargebacks by verifying cardholder identity, but it frequently led to significant user friction. Mobile browser incompatibility, slow authentication page loads, and a perceived lack of necessity often led to transaction abandonment.&lt;/p&gt;

&lt;p&gt;Recognizing these challenges, 3-D Secure 2 (3DS2) leveraged a broader range of data to enable more accurate risk assessment and a smoother mobile experience. It introduced two main authentication paths: the frictionless flow and the challenge flow. In the frictionless flow, transactions can be approved without requiring any additional input from the cardholder. This occurs when a transaction is deemed low-risk through real-time risk-based analysis (RBA). On the flip side, if the RBA identifies a transaction as potentially high-risk, a challenge flow is initiated, prompting the user for further verification, such as an OTP, biometric authentication, or a PIN.&lt;/p&gt;

&lt;p&gt;Choosing between a frictionless or challenge flow depends on numerous data points at the time of the transaction by both merchants and issuers. These data points can include device information, transaction amount, customer status (new or existing), transactional history, and geolocation. For instance, if a new card is used by a customer with no prior transaction history or an existing card is used on an unfamiliar device, the risk can be elevated, leading to a 3DS challenge. However, a customer with a card on file and a history of successful, non-fraudulent transactions can trigger a low-risk assessment, allowing the transaction to proceed via the frictionless flow, bypassing explicit 3DS authentication.&lt;/p&gt;

&lt;p&gt;Developers should strive to minimize challenge flows and maximize frictionless outcomes by transmitting rich contextual data alongside payment information. The richer the data, the more accurately issuers can assess the customer's true risk profile. This not only aids in satisfying issuer rules and regulatory requirements but also reduces checkout friction and improves conversion rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balancing Security and User Experience
&lt;/h2&gt;

&lt;p&gt;Minimizing challenge flows and maximizing frictionless outcomes keeps customers happy while still preventing fraud. In this section, you will learn strategies you can use to strike that balance when implementing 3DS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using 3DS Exemptions to Reduce Friction
&lt;/h3&gt;

&lt;p&gt;3DS exemptions are specific provisions under regulations like PSD2 that allow certain transactions to bypass active cardholder authentication. These exemptions are primarily designed to reduce friction and enhance the user experience for lower-risk transactions.&lt;/p&gt;

&lt;p&gt;Here are some common exemption types:&lt;/p&gt;

&lt;h4&gt;
  
  
  Low-Value Transactions
&lt;/h4&gt;

&lt;p&gt;Developers can request a low-value exemption (LVE) for payments less than or equal to €30 (or its equivalent after currency conversion). This exemption applies as long as the cumulative sum of previous transactions by the same cardholder, since their last SCA, does not exceed a certain threshold (&lt;em&gt;eg&lt;/em&gt; €100) or the number of consecutive low-value transactions does not exceed five, depending on the issuer's enforcement policy.&lt;/p&gt;

&lt;p&gt;To programmatically request this exemption, depending on your Payment Service Provider's (PSP) API, you can set an exemption indicator. For example, with some APIs, you set &lt;code&gt;payment_method_options.sca_exemption&lt;/code&gt; to &lt;code&gt;LowValue&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Transaction Risk Analysis
&lt;/h4&gt;

&lt;p&gt;Through transaction risk analysis (TRA), merchants or their payment providers can request a 3DS exemption for transactions they've internally assessed as low-risk. This exemption is useful for promoting customer retention by ensuring known or trusted customers are not subjected to unnecessary authentication. Merchants must analyze a transaction's risk profile and confirm it as low-risk through their internal systems before requesting a TRA exemption.&lt;/p&gt;

&lt;p&gt;For instance, a $20 USD order from a long-time customer using the same known device, IP address, and billing address, with no history of chargebacks, would be a strong candidate. The merchant would then flag this as low-risk and request the exemption via TRA. Programmatically, this can involve setting &lt;code&gt;payment_method_options.sca_exemption&lt;/code&gt; to &lt;code&gt;TransactionRiskAnalysis&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Credentials on File
&lt;/h4&gt;

&lt;p&gt;Credentials on File (CoF) exemptions apply when charging a stored card from a returning customer. This typically involves the merchant or PSP securely storing a customer's card details for future payments, such as recurring subscriptions, top-ups, or one-click reorders. There are two primary types of CoF transactions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merchant-initiated transactions (MIT):&lt;/strong&gt; This is for payments like a Netflix subscription, where the user is not actively present at the time of payment. The transaction is inherently exempt from SCA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer-initiated transactions (CIT):&lt;/strong&gt; When a customer with their card on file chooses to reorder with a single click, they may qualify for a frictionless flow, provided the transaction meets other low-risk criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While other exemptions, such as trusted beneficiaries, exist, they often require direct customer action (&lt;em&gt;eg&lt;/em&gt; whitelisting a merchant on their issuer's platform) and are less directly controlled by the merchant. This article focuses on merchant-initiated strategies.&lt;/p&gt;

&lt;p&gt;Properly utilizing the exemptions mentioned here helps maximize frictionless flows and leads to a more seamless customer experience and faster payment approvals. However, note that requesting an exemption does not guarantee a frictionless flow. The issuer ultimately retains the right to override the exemption and mandate a challenge if they deem the transaction high risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-Tuning the Challenge Indicator
&lt;/h3&gt;

&lt;p&gt;Challenge indicators are optional parameters that merchants send to issuing banks to influence whether a transaction should undergo additional authentication (a challenge flow). This parameter is typically included within the payment request to your PSP. While specific implementations may vary between PSPs, a common representation can look like this:&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="nl"&gt;"3DRequestorChallengeInd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"02"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following are some common challenge indicators:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Challenge Indicator Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;No preference&lt;/td&gt;
&lt;td&gt;You let the issuer decide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;No challenge requested&lt;/td&gt;
&lt;td&gt;You want a frictionless flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;Challenge requested (merchant preference)&lt;/td&gt;
&lt;td&gt;You prefer a challenge flow (&lt;em&gt;eg&lt;/em&gt; suspicious activity, first-time user, new device)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;Challenge requested (mandate)&lt;/td&gt;
&lt;td&gt;You need a challenge flow as required by law (regulations the merchant is bound by or internal business rule)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's important to note that issuers make the final decision. Even if you request a frictionless flow (&lt;code&gt;02&lt;/code&gt;), the issuer may still challenge the transaction based on their risk analysis.&lt;/p&gt;

&lt;p&gt;While requesting a challenge (&lt;code&gt;03&lt;/code&gt; or &lt;code&gt;04&lt;/code&gt;) can reduce fraud and liability, doing so unnecessarily may negatively impact your conversion rates. In contrast, strategically using &lt;code&gt;02&lt;/code&gt; for low-risk transactions can improve authorization rates, but applying it too broadly to transactions that should have been challenged may lead to more chargebacks or soft declines.&lt;/p&gt;

&lt;p&gt;A good starting point is to default to &lt;code&gt;01&lt;/code&gt; (no preference) or &lt;code&gt;02&lt;/code&gt; (no challenge requested) and adjust your strategy based on observation, user behavior, and fraud risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing More PII Means Fewer Challenges
&lt;/h3&gt;

&lt;p&gt;Personally identifiable information (PII) comprises any data that can identify a specific individual. In the context of 3DS transactions, passing more PII can significantly increase the likelihood of a frictionless flow and improve the authentication experience for users.&lt;/p&gt;

&lt;p&gt;Issuing banks heavily rely on the RBA to determine whether a 3DS transaction should proceed via a frictionless flow or require a challenge. The more context an issuer receives, the more accurately its risk engine (often powered by artificial intelligence (AI) / machine learning (ML) models) can assess the legitimacy of a transaction.&lt;/p&gt;

&lt;p&gt;Merchants can significantly increase the likelihood of a frictionless flow by including a rich set of PII and other contextual data in their 3DS requests. When the issuing bank receives this detailed information, its RBA model can confidently look for signals of legitimacy and fraud, such as the following: Is this a known device? Is there a history of fraudulent activity associated with this IP address, email, or card? Is the transaction amount unusually high?&lt;/p&gt;

&lt;p&gt;The more detailed PII the merchant provides, the better the RBA model performs, leading to more confident risk scoring and, ultimately, higher rates of frictionless authentication.&lt;/p&gt;

&lt;p&gt;Here are some examples of valuable PII and metadata to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email address&lt;/li&gt;
&lt;li&gt;Phone number&lt;/li&gt;
&lt;li&gt;Billing address&lt;/li&gt;
&lt;li&gt;Device fingerprint&lt;/li&gt;
&lt;li&gt;Browser metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers should make every effort to provide as much optional customer metadata as possible in their 3DS requests to ensure the best outcomes and maximize frictionless flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling 3DS Failures and Soft Declines
&lt;/h3&gt;

&lt;p&gt;It's important to understand that a 3DS failure is not always the same as a final payment authorization decline. 3DS failures in online payments typically occur when a cardholder fails to correctly authenticate their transaction due to incorrect information input or because of technical issues within the authentication process itself. These outcomes can generally be categorized into two main types: hard declines and soft declines.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hard Declines
&lt;/h4&gt;

&lt;p&gt;Hard declines are final rejections of a payment where retrying the transaction is unlikely to succeed. Common scenarios include the following: the card not being enrolled in 3DS, the authentication explicitly rejected by the cardholder, or the issuer refusing to honor the transaction due to a card issue (&lt;em&gt;eg&lt;/em&gt; lost, stolen, or account closed).&lt;/p&gt;

&lt;p&gt;For hard declines, it's best not to retry to avoid potential chargebacks or issuer penalties. For example, if a payment declines with a code like &lt;code&gt;63 - Cardholder not Enrolled&lt;/code&gt;, you should return a clear message to the customer, such as "Your card is not enrolled in secure authentication. Please use a different payment method."&lt;/p&gt;

&lt;h4&gt;
  
  
  Soft Declines
&lt;/h4&gt;

&lt;p&gt;Soft declines are usually not final and present an opportunity for retry. For instance, a merchant may initially attempt a frictionless flow for a payment, but the issuer can soft decline the transaction, requesting SCA on retry.&lt;/p&gt;

&lt;p&gt;When faced with soft declines, issuers may require merchants to retry soft declined transactions with 3DS within a specific timeframe, often indicated by a Merchant Advice Code (MAC). MACs are signals sent by card networks like Mastercard in response to a declined transaction. They help merchants understand whether to retry a transaction or not.&lt;/p&gt;

&lt;p&gt;Developers should build logic to handle these soft declines gracefully by retrying the same transaction but adjusting parameters to force a challenge on the second attempt (&lt;em&gt;eg&lt;/em&gt; by setting the 3DS challenge indicator appropriately, which is platform dependent).&lt;/p&gt;

&lt;p&gt;MACs are typically returned in the response of a failed 3DS transaction. These codes provide crucial guidance on the next steps to take. Here are a few examples of common MAC and their meanings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MAC&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;What to Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;Restricted card&lt;/td&gt;
&lt;td&gt;Do not retry the transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;Exceeds withdrawal count limit&lt;/td&gt;
&lt;td&gt;Retry with a 3DS challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;Contact card issuer&lt;/td&gt;
&lt;td&gt;Retry is not advised; may indicate final failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;Authentication not available; 3DS service down&lt;/td&gt;
&lt;td&gt;Retry later or through different processor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep in mind that the codes can vary slightly depending on the payment processor. Always consult your provider's specific documentation. Decoding the MAC after a 3DS request has failed is essential for guiding developers to handle declines gracefully and optimize their payment flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging AI to Optimize 3DS Strategy
&lt;/h3&gt;

&lt;p&gt;AI and ML offer a powerful avenue for optimizing your 3DS strategy. By combining data analysis and continuous learning, AI can help merchants and payment providers make smarter, adaptive decisions across the entire authentication funnel.&lt;/p&gt;

&lt;p&gt;AI/ML can provide value in the following ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exemption prediction:&lt;/strong&gt; AI can analyze historical transaction data, issuer behavior, transaction context, and user history to predict when an exemption (like TRA or LVE) is most likely to succeed or even when to avoid requesting an exemption entirely to avoid soft declines. For example, if an AI model learns that a specific issuer frequently declines TRA exemptions after 10 PM, it can dynamically adjust future attempts during that timeframe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic challenge indicator selection:&lt;/strong&gt; Instead of relying on static, hard-coded values for the 3DS challenge indicator, an AI algorithm can analyze real-time data points, such as device information, customer behavior, and transaction amount, to assess the risk level of each payment. This assessment informs the selection of the most appropriate challenge indicator (&lt;em&gt;eg&lt;/em&gt; &lt;code&gt;no_challenge_requested&lt;/code&gt; for low-risk, &lt;code&gt;challenge_requested&lt;/code&gt; for high-risk). This approach helps developers better align with issuer policies, reduce friction for low-risk transactions, and avoid unnecessary declines due to mismatched challenge expectations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII level tuning:&lt;/strong&gt; AI/ML can learn which specific PII and contextual data are most valued by different issuers. Some issuers can heavily weigh device data, while others prioritize email, IP location, or account tenure. An AI model can dynamically adjust the depth and type of information sent with each transaction to improve trust scores and increase the likelihood of frictionless flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These AI-driven optimizations thrive on real transaction feedback loops. Models continuously learn from outcomes, such as successes, declines, and fraud flags, to identify evolving risk patterns and refine their logic over time. This approach ensures that authentication strategies are constantly improving based on real-time transactions and customer context, rather than static rules.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.rapyd.net/blog/using-ai-to-optimise-payments-performance/" rel="noopener noreferrer"&gt;benefits of integrating AI&lt;/a&gt; into your 3DS strategy include higher conversion rates, effective fraud management, improved issuer alignment, and more frictionless flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sample Project: 3DS Optimization Playground
&lt;/h2&gt;

&lt;p&gt;To help developers visualize the concepts of 3DS optimization, this section explores a simple web-based simulator called the &lt;strong&gt;3DS Optimization Playground&lt;/strong&gt;. This application demonstrates how various parameters, such as transaction amount, CoF status, challenge indicators, and the presence of PII, can influence the outcome of a 3DS flow. All the source code is available on &lt;a href="https://github.com/Rapyd-Samples/Rapyd_3ds_example/blob/main/simulator.html" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;simulator.html&lt;/code&gt; file is designed to run directly in a web browser without requiring a server-side component. Here's a brief explanation of its key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DOM element selection:&lt;/strong&gt; The logic starts by selecting all the necessary HTML elements using &lt;code&gt;document.getElementById&lt;/code&gt; and storing them in constants for easy access:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount&lt;/span&gt;&lt;span class="dl"&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;cofStatusSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cofStatus&lt;/span&gt;&lt;span class="dl"&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;challengeIndicatorSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;challengeIndicator&lt;/span&gt;&lt;span class="dl"&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;piiPresenceCheckbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;piiPresence&lt;/span&gt;&lt;span class="dl"&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;aiModelToggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aiModelToggle&lt;/span&gt;&lt;span class="dl"&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;simulateBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simulateBtn&lt;/span&gt;&lt;span class="dl"&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;outputSection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outputSection&lt;/span&gt;&lt;span class="dl"&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;outcomeSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outcome&lt;/span&gt;&lt;span class="dl"&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;frictionlessRateSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;frictionlessRate&lt;/span&gt;&lt;span class="dl"&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;challengeRateSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;challengeRate&lt;/span&gt;&lt;span class="dl"&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;failureRateSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failureRate&lt;/span&gt;&lt;span class="dl"&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;exemptionReasonDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exemptionReason&lt;/span&gt;&lt;span class="dl"&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;failureReasonDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failureReason&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Global rates for AI model persistence:&lt;/strong&gt; The &lt;code&gt;currentFrictionlessRate&lt;/code&gt;, &lt;code&gt;currentChallengeRate&lt;/code&gt;, and &lt;code&gt;currentFailureRate&lt;/code&gt; variables are the base rates that define the probabilities of friction, challenge, and failure occurring in 3DS. They also get tuned in real time after every transaction so that the AI model simulator simulates a cumulative effect on the rates over time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Event listener:&lt;/strong&gt; The &lt;code&gt;simulateBtn.addEventListener('click', simulate3DS)&lt;/code&gt; line attaches an event listener to the "&lt;strong&gt;Simulate 3DS Flow&lt;/strong&gt;" button in the UI. When the button is clicked, the &lt;code&gt;simulate3DS&lt;/code&gt; function is called.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;simulate3DS&lt;/code&gt; function:&lt;/strong&gt; This is the main function that performs the simulation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input retrieval:&lt;/strong&gt; The function initially reads the current value of all input fields (&lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;cofStatus&lt;/code&gt;, &lt;code&gt;challengeIndicator&lt;/code&gt;, &lt;code&gt;piiPresence&lt;/code&gt;, &lt;code&gt;aiModelToggle&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initialization:&lt;/strong&gt; This sets up variables to store the outcome and any specific reasons for the current run in variable &lt;code&gt;outcome&lt;/code&gt;, &lt;code&gt;exemptionReason&lt;/code&gt;, and &lt;code&gt;failureReason&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemption evaluation:&lt;/strong&gt; The next part of the function (lines 314–343) determines if the transaction is eligible for various 3DS exemptions (TRA, LVE, recurring payments). It sets &lt;code&gt;exemptionApplied&lt;/code&gt; and &lt;code&gt;exemptionReason&lt;/code&gt; according to the exemption triggered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACS random challenge logic:&lt;/strong&gt; Access Control Server (ACS) in payments is a system operated by the issuer to verify the cardholder's identity. In the function, the ACS code block (lines 346–352) implements a one-in-four chance for the ACS to mandate a challenge if no exemption applies and the challenge indicator is "no preference" or "no challenge".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outcome determination:&lt;/strong&gt; Based on the combination of the challenge indicator, exemption eligibility, and the ACS-mandated challenge, the code block determines the final &lt;code&gt;outcome&lt;/code&gt; of the 3DS flow (e.g., "Frictionless (Exempted)", "Challenge (Mandated)", "Frictionless", "Failure").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI model impact (simplified):&lt;/strong&gt; If the "Enable AI model" toggle is enabled, this section simulates how an AI may auto-tune parameters. It heuristically adjusts the global frictionless, challenge, and failure rates to favor more frictionless outcomes, demonstrating the concept of optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate normalization:&lt;/strong&gt; Ensure the sum of frictionless, challenge, and failure rates always adds up to 100 percent to maintain probability consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display results:&lt;/strong&gt; Updates the application's output section with the calculated outcome and rates. It also displays the &lt;code&gt;exemptionReason&lt;/code&gt; or &lt;code&gt;failureReason&lt;/code&gt; if applicable.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test Cases for the 3DS Simulator
&lt;/h3&gt;

&lt;p&gt;The following scenarios are designed to help you visualize the logic in the simulation. Open &lt;code&gt;simulator.html&lt;/code&gt; with your preferred (modern) browser to play around with it. For each test case, enter the specified input values and click the &lt;strong&gt;Simulate 3DS Flow&lt;/strong&gt; button to see the expected outcome.&lt;/p&gt;

&lt;h4&gt;
  
  
  Low-Value Exemption
&lt;/h4&gt;

&lt;p&gt;This test case demonstrates a transaction that is automatically exempted from a challenge because the transaction value is low.&lt;/p&gt;

&lt;h5&gt;
  
  
  Inputs
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction amount:&lt;/strong&gt; &lt;code&gt;$25.00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CoF status:&lt;/strong&gt; &lt;code&gt;Not CoF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge indicator:&lt;/strong&gt; &lt;code&gt;No Preference&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII present?:&lt;/strong&gt; Unchecked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable AI model:&lt;/strong&gt; Unchecked&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Expected Outcome
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outcome:&lt;/strong&gt; &lt;code&gt;Frictionless (Exempted)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemption/optimization info:&lt;/strong&gt; &lt;code&gt;Low Value Exemption (LVE): Transaction amount is below the low value threshold.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simulator's logic checks for the LVE initially. Since the amount is less than or equal to $30 USD, the transaction is immediately classified as frictionless without any further checks:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F0bkcskscunfejnsasgml.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.amazonaws.com%2Fuploads%2Farticles%2F0bkcskscunfejnsasgml.png" alt="Image of test case 1 with results" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  CoF Exemption
&lt;/h4&gt;

&lt;p&gt;This scenario shows how a recurring payment from a trusted customer can be exempted from a challenge, even with a higher transaction amount.&lt;/p&gt;

&lt;h5&gt;
  
  
  Inputs
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction amount:&lt;/strong&gt; &lt;code&gt;$75.00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CoF status:&lt;/strong&gt; &lt;code&gt;Merchant Initiated Transaction (MIT)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge indicator:&lt;/strong&gt; &lt;code&gt;No Preference&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII present?:&lt;/strong&gt; Unchecked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable AI model:&lt;/strong&gt; Unchecked&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Expected Outcome
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outcome:&lt;/strong&gt; &lt;code&gt;Frictionless (Exempted)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemption/optimization info:&lt;/strong&gt; &lt;code&gt;Recurring Payments/MIT Exemption: Transaction is a CoF payment.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simulator prioritizes CoF transactions as a valid exemption reason. Even though the amount is above the LVE threshold, the &lt;code&gt;cofStatus&lt;/code&gt; of merchant-initiated signals a recurring payment, which is considered low-risk and therefore frictionless:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fv5nck7rpwhiu5yl44m3m.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.amazonaws.com%2Fuploads%2Farticles%2Fv5nck7rpwhiu5yl44m3m.png" alt="Image of test case 2 with results" width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Challenge Mandated
&lt;/h4&gt;

&lt;p&gt;This demonstrates a scenario where a challenge is unavoidable, regardless of other factors, because it has been explicitly requested.&lt;/p&gt;

&lt;h5&gt;
  
  
  Inputs
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction amount:&lt;/strong&gt; &lt;code&gt;$15.00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CoF status:&lt;/strong&gt; &lt;code&gt;Not CoF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge indicator:&lt;/strong&gt; &lt;code&gt;Challenge Mandated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII present?:&lt;/strong&gt; Checked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable AI model:&lt;/strong&gt; Unchecked&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Expected Outcome
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outcome:&lt;/strong&gt; &lt;code&gt;Challenge (Mandated)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemption/optimization info:&lt;/strong&gt; No message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Challenge (Mandated)&lt;/code&gt; indicator acts as an override. It bypasses all exemption logic and forces a challenge, which is useful for transactions deemed high-risk by the merchant or when required by regulations:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fv2321hwenhfz7vhxzvgt.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.amazonaws.com%2Fuploads%2Farticles%2Fv2321hwenhfz7vhxzvgt.png" alt="Image of test case 3 with results" width="800" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  AI Model Tuning
&lt;/h4&gt;

&lt;p&gt;This test highlights the dynamic nature of the AI model. You have to run this simulation with the AI toggle checked multiple times to see the effect.&lt;/p&gt;

&lt;h5&gt;
  
  
  Inputs
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction amount:&lt;/strong&gt; &lt;code&gt;$100.00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CoF status:&lt;/strong&gt; &lt;code&gt;Not CoF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge indicator:&lt;/strong&gt; &lt;code&gt;No Preference&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII present?:&lt;/strong&gt; Checked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable AI model:&lt;/strong&gt; Checked&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Expected Outcome
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outcome:&lt;/strong&gt; Over multiple runs, the &lt;code&gt;Frictionless Rate&lt;/code&gt; gradually increases, and the &lt;code&gt;Challenge Rate&lt;/code&gt; and &lt;code&gt;Failure Rate&lt;/code&gt; decrease with each successful frictionless or challenged outcome.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exemption/optimization info:&lt;/strong&gt; If a frictionless outcome occurs without an exemption, the message is &lt;code&gt;AI Model Optimization: Transaction identified as low risk for frictionless flow.&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the AI model enabled, it learns from each transaction outcome by adjusting the underlying success rates:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fwatda9m3k49e3f2rsii8.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.amazonaws.com%2Fuploads%2Farticles%2Fwatda9m3k49e3f2rsii8.png" alt="Image of test case 4 with results" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;When implementing 3DS, it's easy to become focused on the enhanced security that additional authentication layers provide, inadvertently overlooking the friction that comes with it. A balance must be struck: merchants who lean too heavily toward either extreme risk, rampant insecurity, and fraud in their payment funnel, or those who suffer from high abandonment rates due to excess customer friction.&lt;/p&gt;

&lt;p&gt;In this article, you learned how to achieve the optimal balance between security and a great user experience. You saw strategies, like 3DS exemptions, challenge indicator parameters, data enrichment in payment requests using PII, and 3DS failure management, and you learned how to harness the power of AI and ML models for 3DS optimization.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
      <category>3ds</category>
    </item>
    <item>
      <title>Mastering Dispute Resolution with Mastercard Collaboration</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Tue, 28 Oct 2025 16:44:29 +0000</pubDate>
      <link>https://dev.to/rapyd/mastering-dispute-resolution-with-mastercard-collaboration-5f70</link>
      <guid>https://dev.to/rapyd/mastering-dispute-resolution-with-mastercard-collaboration-5f70</guid>
      <description>&lt;p&gt;By: &lt;strong&gt;Vivek Kumar Maskara&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a security measure, card issuers allow their users to dispute a transaction in case of fraud, theft, or other legitimate scenarios. When a customer disputes a transaction, you, as the merchant, need to act fast to review the dispute before it escalates into a &lt;a href="https://www.rapyd.net/blog/chargeback-fraud/" rel="noopener noreferrer"&gt;chargeback&lt;/a&gt;, which refers to a forced reversal of funds initiated by the cardholder's bank. If your chargeback ratio rises too high, you could face penalties or even lose your ability to receive card payments altogether.&lt;/p&gt;

&lt;p&gt;Rapid Dispute Resolution (RDR) enables merchants to automate their dispute response with predefined rules that allow instant review and automatic refunds for certain scenarios. &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; is an all-in-one payment platform that integrates directly with &lt;a href="https://usa.visa.com/" rel="noopener noreferrer"&gt;Visa&lt;/a&gt; and &lt;a href="https://www.mastercard.us/en-us.html" rel="noopener noreferrer"&gt;Mastercard&lt;/a&gt; acquirers, helping merchants improve their authorization rates and act against chargebacks.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to use the &lt;a href="https://www.rapyd.net/developers/" rel="noopener noreferrer"&gt;Rapyd API&lt;/a&gt; to automatically resolve low-value Mastercard transaction disputes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand Dispute Resolution with Mastercard Collaboration
&lt;/h2&gt;

&lt;p&gt;RDR is a fully automated dispute management solution for merchants that was initially championed by Visa in collaboration with &lt;a href="https://www.verifi.com/rapid-dispute-resolution-is-here/" rel="noopener noreferrer"&gt;Verifi&lt;/a&gt;. &lt;a href="https://developer.mastercard.com/mastercom/documentation/getting-started/#collaboration" rel="noopener noreferrer"&gt;Mastercard Collaboration&lt;/a&gt; offers a similar capability for Mastercard-issued cards, enabling merchants to resolve disputes before a chargeback can occur. The following diagram illustrates the dispute resolution flow with Mastercard Collaboration:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F862q341qvniikzgubqmu.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.amazonaws.com%2Fuploads%2Farticles%2F862q341qvniikzgubqmu.png" alt="Dispute resolution with Mastercard Collaboration, courtesy of Vivek Maskara" width="800" height="1068"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Collaboration alerts the merchant as soon as the transaction is disputed, allowing merchants to review the cardholder complaint and issue a refund if needed. If the merchant refunds the transaction, a chargeback is no longer needed, and Mastercard marks the dispute as resolved. If the refund is not processed, the dispute advances to the chargeback step, and the Collaboration process is completed.&lt;/p&gt;

&lt;p&gt;As a merchant receiving payments through multiple payment methods, including Visa and Mastercard, integrating with multiple dispute resolution solutions can be challenging. Rapyd simplifies this by offering direct integration with both Visa and Mastercard, enabling seamless dispute resolution implementation within your application:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F5wotop2xvtdy3any6c4b.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.amazonaws.com%2Fuploads%2Farticles%2F5wotop2xvtdy3any6c4b.png" alt="Mastercard dispute resolution with Rapyd, courtesy of Vivek Maskara" width="800" height="1686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rapyd lets merchants define automatic refund rules using the Client Portal to determine the conditions under which an automatic refund should be issued. When Rapyd receives a dispute notification from Mastercard Collaboration, it evaluates the merchant-defined rules, and if the criteria are met, it issues an automatic refund without merchant intervention. Otherwise, it notifies the merchant by sending a &lt;code&gt;PAYMENT_DISPUTE_CREATED&lt;/code&gt; &lt;a href="https://docs.rapyd.net/en/dispute-created-webhook.html" rel="noopener noreferrer"&gt;webhook event&lt;/a&gt;, and the merchant can perform additional checks before deciding the outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement Mastercard Dispute Resolution with Rapyd
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you'll create an &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express&lt;/a&gt; application that makes a Mastercard card payment request using &lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;Rapyd APIs&lt;/a&gt; and listens to &lt;a href="https://docs.rapyd.net/en/webhook-format.html" rel="noopener noreferrer"&gt;webhook events&lt;/a&gt; for dispute resolution. By the end of the tutorial, you'll learn how to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make a signed Rapyd API request for payment and disputes.&lt;/li&gt;
&lt;li&gt;Set up a webhook that listens to payment and dispute creation events and lets you take custom actions.&lt;/li&gt;
&lt;li&gt;Send an email to the merchant notifying them about the dispute details.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Before starting, you need to complete the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign up for a &lt;a href="https://dashboard.rapyd.net/sign-up/" rel="noopener noreferrer"&gt;Rapyd account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.rapyd.net/en/set-up-your-account.html" rel="noopener noreferrer"&gt;Set up your Rapyd account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js 18.0 or above&lt;/a&gt; on your development machine.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrok.com/docs/getting-started/" rel="noopener noreferrer"&gt;Install and set up ngrok&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial uses &lt;a href="https://github.com/Rapyd-Samples/dispute-resolution-mastercard" rel="noopener noreferrer"&gt;this starter code&lt;/a&gt; from GitHub that has a barebones Node.js app set up. To follow along, clone the GitHub repo and switch to the &lt;code&gt;starter&lt;/code&gt; branch by following these steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Rapyd-Samples/dispute-resolution-mastercard
&lt;span class="nb"&gt;cd &lt;/span&gt;rapyd-payments
git checkout starter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go over the structure of the codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;package.json&lt;/code&gt; file defines the Express project dependencies and the &lt;code&gt;npm start&lt;/code&gt; script.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;src&lt;/code&gt; directory contains the main source code, including the &lt;code&gt;index.js&lt;/code&gt; entry point, which initializes the Express server by creating a new instance of the &lt;code&gt;App&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;src/services/rapyd.utilities.js&lt;/code&gt; file defines methods to make a signed HTTPS request to Rapyd servers. It defines the &lt;code&gt;makeRequest&lt;/code&gt; method that takes the HTTP &lt;code&gt;method&lt;/code&gt; type, &lt;code&gt;url&lt;/code&gt;, and &lt;code&gt;body&lt;/code&gt; as parameters, and it returns the Rapyd API response to the caller. This method uses the &lt;code&gt;sign&lt;/code&gt; method to sign the payload before making the REST API call. If you want to learn more about the signing process, check out &lt;a href="https://docs.rapyd.net/en/request-signatures.html" rel="noopener noreferrer"&gt;this Rapyd guide&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set Up Your Rapyd Access Key and Secret Key
&lt;/h3&gt;

&lt;p&gt;Notice that the source contains &lt;code&gt;.env.example&lt;/code&gt;, which is a sample environment file. Rename the file to &lt;code&gt;.env&lt;/code&gt; and replace the values for &lt;code&gt;RAPYD_API_KEY&lt;/code&gt; and &lt;code&gt;RAPYD_SECRET_KEY&lt;/code&gt; with your &lt;a href="https://docs.rapyd.net/en/access-keys.html" rel="noopener noreferrer"&gt;credentials&lt;/a&gt;, which you can access on the Rapyd Client Portal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;RAPYD_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REPLACE_WITH_RAPYD_ACCESS_KEY&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;RAPYD_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REPLACE_WITH_RAPYD_SECRET_KEY&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;BASERAPYDAPIURL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://sandboxapi.rapyd.net&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Dependencies and Run the App
&lt;/h3&gt;

&lt;p&gt;After familiarizing yourself with the code, install the npm dependencies by executing the following command:&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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now run the starter code to verify your setup. Execute the following command to run the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output looks 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;App initialized
🚀 Server running on http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the &lt;code&gt;.env&lt;/code&gt; file defines the application port as &lt;code&gt;8000&lt;/code&gt;, the server starts running on this port. You can open a new terminal window and use curl to verify that the default endpoint is accessible by executing the following:&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;-i&lt;/span&gt; http://localhost:8000/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output looks 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;Welcome to the Rapyd Payment API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the starter code is set up, you can proceed to integrate Rapyd payment and dispute resolution APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate Rapyd APIs to Receive Payments
&lt;/h2&gt;

&lt;p&gt;This section explains how to integrate Rapyd APIs to receive payments, retrieve dispute status, and issue refunds. For this tutorial, the Node.js application doesn't define strict request schemas and simply relays the request payload to Rapyd. You'll integrate the following Rapyd APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;POST v1/payments/&lt;/code&gt;&lt;/strong&gt; to &lt;a href="https://docs.rapyd.net/en/create-payment.html" rel="noopener noreferrer"&gt;create a new card payment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GET v1/payments/&lt;/code&gt;&lt;/strong&gt; to &lt;a href="https://docs.rapyd.net/en/retrieve-payment.html" rel="noopener noreferrer"&gt;retrieve details of a payment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GET v1/payments/&lt;/code&gt;&lt;/strong&gt; to &lt;a href="https://docs.rapyd.net/en/retrieve-payment.html" rel="noopener noreferrer"&gt;retrieve details of a payment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;POST v1/refunds&lt;/code&gt;&lt;/strong&gt; to &lt;a href="https://docs.rapyd.net/en/create-refund.html" rel="noopener noreferrer"&gt;create a refund&lt;/a&gt; (Note that this tutorial doesn't directly use the refund endpoint, but the integration details are covered here for completeness.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GET v1/disputes/{disputeID}&lt;/code&gt;&lt;/strong&gt; to &lt;a href="https://docs.rapyd.net/en/retrieve-dispute.html" rel="noopener noreferrer"&gt;retrieve details of a dispute&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before starting the integration, refer to the linked API docs earlier for details about the request parameters.&lt;/p&gt;

&lt;p&gt;You can utilize the &lt;code&gt;makeRequest&lt;/code&gt; method defined in the starter code to send a signed request to Rapyd servers. To integrate the APIs, update the &lt;code&gt;src/services/rapyd.service.js&lt;/code&gt; file with the following code snippet:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;makeRequest&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="s1"&gt;./rapyd.utilities.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RapydService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentData&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/payments&lt;/span&gt;&lt;span class="dl"&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;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;paymentData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getPaymentStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentId&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/v1/payments/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paymentId&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refundPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refundData&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/v1/refunds&lt;/span&gt;&lt;span class="dl"&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;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;refundData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getDisputeById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;disputeId&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/v1/disputes/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;disputeId&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;makeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&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;default&lt;/span&gt; &lt;span class="nx"&gt;RapydService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The newly defined methods relay the request payload to the &lt;code&gt;makeRequest&lt;/code&gt; method and return the response received from it. Next, you need to update the controller to use the service methods for payment, disputes, and refunds. Update the &lt;code&gt;src/controllers/payment.controller.js&lt;/code&gt; with the following code snippet:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;RapydService&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../services/rapyd.service.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createPayment&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;payment&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;RapydService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPayment&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;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;201&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;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payment&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getPaymentStatus&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;paymentStatus&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;RapydService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPaymentStatus&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;params&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="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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentStatus&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refundPayment&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;refund&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;RapydService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refundPayment&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;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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refund&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getDisputeById&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;dispute&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;RapydService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDisputeById&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;params&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="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;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dispute&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;export&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;createPayment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getPaymentStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;refundPayment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getDisputeById&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PaymentController&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;default&lt;/span&gt; &lt;span class="nx"&gt;PaymentController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code snippet, every controller method parses the &lt;code&gt;req&lt;/code&gt; object and utilizes the &lt;code&gt;RapydService&lt;/code&gt; methods to define &lt;code&gt;createPayment&lt;/code&gt;, &lt;code&gt;getPaymentStatus&lt;/code&gt;, &lt;code&gt;refundPayment&lt;/code&gt;, and &lt;code&gt;getDisputeById&lt;/code&gt; methods. If the &lt;code&gt;RapydService&lt;/code&gt; throws an error, the controller method catches the error and returns a &lt;code&gt;next(error)&lt;/code&gt; API error response.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;src/routes.js&lt;/code&gt; file to define the new API routes:&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;// Add this import statement&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;createPayment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getPaymentStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;refundPayment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getDisputeById&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="s1"&gt;./controllers/payment.controller.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Add payment routes&lt;/span&gt;
&lt;span class="nx"&gt;router&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="s1"&gt;/payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createPayment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&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="s1"&gt;/payment/:id/refund&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refundPayment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&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="s1"&gt;/status/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getPaymentStatus&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&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="s1"&gt;/disputes/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getDisputeById&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After these routes are added, the Express application can be used to create card payments and check their status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Payment Integration
&lt;/h2&gt;

&lt;p&gt;Now that you've added the payment endpoints in the Node.js application, you can test payments using &lt;a href="https://developer.mastercard.com/mastercard-send/documentation/implementation/sandbox-test-cards/" rel="noopener noreferrer"&gt;Mastercard sandbox cards&lt;/a&gt;. Execute the following curl command that uses one of the sandbox cards to create a payment:&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8000/api/payment'&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;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
    "amount": 26.51,
    "currency": "ZAR",
    "merchant_reference_id": "042620221450",
    "payment_method": {
        "type": "gb_mastercard_card",
        "fields": {
            "number": "5102589999999913",
            "expiration_month": "11",
            "expiration_year": "26",
            "cvv": "123",
            "name": "John Doe"
        }
    },
    "ewallets": [
        {
            "ewallet": "&amp;lt;YOUR_RAPYD_EWALLET_ID&amp;gt;",
            "percentage": 100
        }
    ],
    "metadata": {
        "merchant_defined": "created"
    },
    "capture": true,
    "payment_method_options": {
        "3d_required": false
    }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace &lt;code&gt;&amp;lt;YOUR_RAPYD_EWALLET_ID&amp;gt;&lt;/code&gt; with a &lt;a href="https://docs.rapyd.net/en/rapyd-wallet.html" rel="noopener noreferrer"&gt;Rapyd Wallet ID&lt;/a&gt;. You can create a new wallet or retrieve an existing wallet ID using the &lt;a href="https://dashboard.rapyd.net/ewallets/accounts" rel="noopener noreferrer"&gt;Rapyd Developer Portal&lt;/a&gt;. Refer to the &lt;a href="https://docs.rapyd.net/en/create-payment.html" rel="noopener noreferrer"&gt;&lt;strong&gt;Create Payment&lt;/strong&gt; API reference&lt;/a&gt; to learn more about the request payload.&lt;/p&gt;

&lt;p&gt;After you execute the curl command, you should receive a success response with payment details and status:&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;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&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="nl"&gt;"body"&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="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;"payment_1e8c9b2cf200882954bfe49fa550476c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"original_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.51&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="nl"&gt;"customer_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cus_33ea7f84366b2b092a2c210faa5c31b1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"payment_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;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;You can also check the payment details using the &lt;a href="https://dashboard.rapyd.net/collect/payments/list" rel="noopener noreferrer"&gt;&lt;strong&gt;Collect &amp;gt; Payments&lt;/strong&gt; page&lt;/a&gt; on the Rapyd Client Portal. Similarly, you can test other API endpoints by grabbing sample payloads from the &lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;API docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up a Webhook to Notify the Merchant About Disputes
&lt;/h2&gt;

&lt;p&gt;Now that you have the payment integration set up, you can create a &lt;a href="https://docs.rapyd.net/en/webhooks.html" rel="noopener noreferrer"&gt;webhook&lt;/a&gt; to listen to Rapyd payment and dispute events, and send an email with dispute details to the merchant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Email Service
&lt;/h3&gt;

&lt;p&gt;In this tutorial, you'll learn how to send emails using &lt;a href="https://nodemailer.com/" rel="noopener noreferrer"&gt;Nodemailer&lt;/a&gt;, but in a real-world application, you can perform other custom actions upon receiving a webhook event.&lt;/p&gt;

&lt;p&gt;To start, install the &lt;code&gt;nodemailer&lt;/code&gt; npm package by executing the following command:&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;nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command installs the package and updates &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Next, create a &lt;code&gt;services/email.service.js&lt;/code&gt; file and add the following code snippet to it:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodemailer&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;merchantEmail&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="s1"&gt;../config.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&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="nf"&gt;sendDisputeNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;disputeData&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;original_dispute_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;original_transaction_id&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;disputeData&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;emailContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
        Payment Dispute Alert

        Dispute Details:
        - Dispute ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        - Original Transaction ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;original_transaction_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        - Dispute Amount: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;original_dispute_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="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
        - Date: &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="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;

        Action Required:
        A dispute has been created for one of your transactions. Please review the dispute details and take appropriate action.

        This is an automated notification for demonstration purposes.`&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;mailOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;merchantEmail&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="s2"&gt;`Payment Dispute Created - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailContent&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;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailOptions&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dispute notification email sent:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&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="na"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&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;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error sending dispute notification email:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmailService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates a new &lt;a href="https://nodemailer.com/smtp/envelope#complete-example" rel="noopener noreferrer"&gt;mail transport&lt;/a&gt; by calling the &lt;code&gt;createTransport&lt;/code&gt; method in the service constructor. This transport is used to deliver an email over SMTP.&lt;/p&gt;

&lt;p&gt;This code also defines a &lt;code&gt;sendDisputeNotification&lt;/code&gt; method that takes &lt;code&gt;disputeData&lt;/code&gt; as input, constructs the email &lt;code&gt;body&lt;/code&gt; and &lt;code&gt;subject&lt;/code&gt;, and calls the &lt;code&gt;sendMail&lt;/code&gt; to send an email to the merchant.&lt;/p&gt;

&lt;p&gt;Note that the email service requires an &lt;code&gt;emailConfig&lt;/code&gt; to be defined with sender and receiver email details. Add the following details in the &lt;code&gt;config.js&lt;/code&gt; file to configure the email details:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;service&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;EMAIL_SERVICE&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gmail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user&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;EMAIL_USER&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pass&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;EMAIL_PASS&lt;/span&gt; &lt;span class="o"&gt;||&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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merchantEmail&lt;/span&gt; &lt;span class="o"&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;MERCHANT_EMAIL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following environment variables to the &lt;code&gt;.env&lt;/code&gt; file to configure the email service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EMAIL_SERVICE=gmail
EMAIL_USER=your_email@gmail.com
EMAIL_PASS=your_email_password_here
MERCHANT_EMAIL=merchant@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if you're using Gmail, you need to &lt;a href="https://nodemailer.com/usage/using-gmail" rel="noopener noreferrer"&gt;configure OAuth 2.0 or generate an app password&lt;/a&gt;. This tutorial uses Gmail, but you can &lt;a href="https://nodemailer.com/transports" rel="noopener noreferrer"&gt;refer to the Nodemailer docs&lt;/a&gt; if you need to configure a custom SMTP transport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle the Dispute Webhook
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PAYMENT_DISPUTE_CREATED&lt;/code&gt; &lt;a href="https://docs.rapyd.net/en/dispute-created-webhook.html" rel="noopener noreferrer"&gt;webhook event&lt;/a&gt; contains information about the dispute, payment, and payment method. For example, you can retrieve the &lt;code&gt;original_transaction_id&lt;/code&gt; to fetch transaction details.&lt;/p&gt;

&lt;p&gt;Add the &lt;code&gt;handleDisputeCreated&lt;/code&gt; method in the &lt;code&gt;controllers/webhook.controller.js&lt;/code&gt; file as follows:&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;// add import&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;EmailService&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../services/email.service.js&lt;/span&gt;&lt;span class="dl"&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;handleDisputeCreated&lt;/span&gt; &lt;span class="o"&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;disputeData&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;disputeData&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;`Dispute ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;EmailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendDisputeNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;disputeData&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;`Email notification sent for dispute: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error handling dispute:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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 method calls the &lt;code&gt;EmailService.sendDisputeNotification&lt;/code&gt; method with the &lt;code&gt;disputeData&lt;/code&gt; to notify the merchant about the dispute.&lt;/p&gt;

&lt;p&gt;Next, update the &lt;code&gt;handleWebhook&lt;/code&gt; method to call the dispute handler like this:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleWebhook&lt;/span&gt; &lt;span class="o"&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;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;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;switch &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="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;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PAYMENT_DISPUTE_CREATED&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment dispute created:&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;handleDisputeCreated&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="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="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;Now that the webhook is configured to send emails automatically, you can proceed to test the integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Automatic Dispute Resolution for Mastercard Transactions
&lt;/h2&gt;

&lt;p&gt;Before testing the integration, you need to register the webhook endpoint on the Rapyd Client Portal to receive dispute events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Register the Webhook
&lt;/h3&gt;

&lt;p&gt;You need to expose Express on a public URL so that Rapyd can send webhook events to it. You can use &lt;a href="https://ngrok.com/docs/getting-started/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; to expose your web server as a public URL by executing the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you execute the command, it creates a public proxy, and your output looks 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;ngrok
...
Session Status                online
---OUTPUT TRUNCATED---
Forwarding                    https://3939-2601-602-9700-5c30-b883-8d8b-8def-708b.ngrok-free.app -&amp;gt; http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the forwarding URL and navigate to the Rapyd Client Portal to register it. Under &lt;strong&gt;Developers &amp;gt; Webhooks &amp;gt; Management&lt;/strong&gt;, click &lt;strong&gt;Edit URL&lt;/strong&gt; and update the endpoint to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;YOUR_FORWARDING_HOST_NAME&amp;gt;/api/webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_FORWARDING_HOST_NAME&amp;gt;&lt;/code&gt; with the forwarding URL's hostname. Make sure you save the changes for it to take effect:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F881naqa9q1z17ghzy833.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.amazonaws.com%2Fuploads%2Farticles%2F881naqa9q1z17ghzy833.png" alt="Register webhook" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure that under &lt;strong&gt;Collect&lt;/strong&gt;, the &lt;strong&gt;Dispute Created&lt;/strong&gt; webhook event is checked to ensure that your web server receives it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Automatic Dispute Resolution
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.rapyd.net/en/simulating-cardholder-disputes.html" rel="noopener noreferrer"&gt;&lt;strong&gt;Simulating Cardholder Disputes&lt;/strong&gt;&lt;/a&gt; guide lists a few card numbers that you can use to simulate a card payment dispute. When one of these cards is used to create a payment, Rapyd simulates a dispute flow by automatically creating a dispute for the completed payment.&lt;/p&gt;

&lt;p&gt;To simulate a transaction dispute with a &lt;a href="https://docs.rapyd.net/en/mastercard.html" rel="noopener noreferrer"&gt;Mastercard card&lt;/a&gt;, execute the following curl command:&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;YOUR_FORWARDING_HOST_NAME&amp;gt;/api/payment'&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;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data-raw&lt;/span&gt; &lt;span class="s1"&gt;'{
    "amount": 26.51,
    "currency": "ZAR",
    "merchant_reference_id": "042620221450",
    "payment_method": {
        "type": "gb_mastercard_card",
        "fields": {
            "number": "5132803130357186",
            "expiration_month": "11",
            "expiration_year": "26",
            "cvv": "123",
            "name": "John Doe"
        }
    },
    "ewallets": [
        {
            "ewallet": "&amp;lt;YOUR_RAPYD_EWALLET_ID&amp;gt;",
            "percentage": 100
        }
    ],
    "metadata": {
        "merchant_defined": "created"
    },
    "capture": true,
    "payment_method_options": {
        "3d_required": false
    }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_FORWARDING_HOST_NAME&amp;gt;&lt;/code&gt; with the ngrok hostname and &lt;code&gt;&amp;lt;YOUR_RAPYD_EWALLET_ID&amp;gt;&lt;/code&gt; with your Rapyd wallet ID before executing the curl command. Once you execute the command, a payment is created, and within seconds, you'll receive a payment dispute webhook event for it. The dispute details are logged by the web server and look similar to this:&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="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&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="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="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;ca&lt;/span&gt;&lt;span class="mi"&gt;-0e7&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;-4&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;27-8476-2&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;3309e465&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&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;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'dispute_&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;36175&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;2121&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;dffa&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;624&lt;/span&gt;&lt;span class="err"&gt;eb&lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="err"&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ACT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ZAR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dispute_category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Cardholder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Dispute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dispute_reason_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Cardholder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Dispute'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"original_transaction_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ZAR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"original_transaction_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"original_dispute_amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;26.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"original_dispute_currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ZAR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"original_transaction_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'payment_e&lt;/span&gt;&lt;span class="mi"&gt;5e014&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;419632&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;428023&lt;/span&gt;&lt;span class="err"&gt;fcd&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;af&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="err"&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;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;Because you configured the &lt;code&gt;handleDisputeCreated&lt;/code&gt; method for &lt;strong&gt;Dispute Created&lt;/strong&gt; events, it sends an email and logs the following to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dispute notification email sent: 250 2.0.0 OK  1751833493 d9443c01a7336-23c8455d09esm68323335ad.90 - gsmtp
Email notification sent for dispute: dispute_e9388a0a90c7366a96541f6b759c7de4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configured &lt;code&gt;MERCHANT_EMAIL&lt;/code&gt; receives an email as shown here:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Ff54waxi7c057gvxohg7t.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.amazonaws.com%2Fuploads%2Farticles%2Ff54waxi7c057gvxohg7t.png" alt="Email notification when a dispute is created" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also review the dispute details using the &lt;strong&gt;Collect &amp;gt; Review &amp;amp; Protect &amp;gt; Disputes&lt;/strong&gt; tabs on the &lt;a href="https://dashboard.rapyd.net/collect/payments/disputes" rel="noopener noreferrer"&gt;Rapyd Client Portal&lt;/a&gt;:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2Fs1p627xrkixvn0aqwd2s.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.amazonaws.com%2Fuploads%2Farticles%2Fs1p627xrkixvn0aqwd2s.png" alt="Review disputes using the Rapyd Client Portal" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Client Portal lets you update the status of the dispute, submit evidence, or refund a dispute.&lt;/p&gt;

&lt;p&gt;You can find the entire &lt;a href="https://github.com/Rapyd-Samples/dispute-resolution-mastercard" rel="noopener noreferrer"&gt;source code&lt;/a&gt; used in this tutorial on GitHub.&lt;/p&gt;

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

&lt;p&gt;Merchants need to proactively review and resolve card disputes to avoid chargebacks and revenue loss. They can leverage automatic dispute resolution tools like RDR and &lt;a href="https://developer.mastercard.com/mastercom/documentation/getting-started/" rel="noopener noreferrer"&gt;Mastercard Collaboration&lt;/a&gt; to configure rules that instantly evaluate dispute requests and issue automatic refunds if the criteria are met.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; enables businesses to automate dispute resolution by offering direct integration with Visa and Mastercard Collaboration programs, industry-leading authorization rates, and a robust all-in-one payment platform.&lt;/p&gt;

&lt;p&gt;Try the &lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;Rapyd API&lt;/a&gt; and explore the &lt;a href="https://github.com/Rapyd-Samples/dispute-resolution-mastercard" rel="noopener noreferrer"&gt;code sample&lt;/a&gt; to see how Rapyd offers you the best in dispute resolution.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
      <category>disputes</category>
    </item>
    <item>
      <title>Client Portal: Stablecoin Payouts</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Thu, 16 Oct 2025 21:32:05 +0000</pubDate>
      <link>https://dev.to/rapyd/client-portal-stablecoin-payouts-2pbk</link>
      <guid>https://dev.to/rapyd/client-portal-stablecoin-payouts-2pbk</guid>
      <description>&lt;p&gt;Stablecoin payouts are the next evolution of the financial world: using cryptocurrency to make cross border transactions, using a digital, decentralized currency that allows you true financial freedom. By signing up for a Client Portal account, you can integrate with Rapyd today and begin using stablecoin payouts. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Stablecoin?
&lt;/h2&gt;

&lt;p&gt;Stablecoin is a specific type of cryptocurrency that functions slightly differently than most cryptocurrencies. Most crypto derives their entire value from a form of digital scarcity. A limited number of the cryptocurrency is released and the users have to “mine” for this cryptocurrency by using algorithms to “search” for the cryptocurrency. When the crypto is found and verified, the user gets to keep the cryptocurrency. The most recognizable cryptocurrency that uses this model is Bitcoin. &lt;/p&gt;

&lt;p&gt;An issue with cryptocurrency is its volatility. Although crypto can and does hold value, the value does not remain stable over time, but fluctuates wildly. This is partly due to cryptocurrency exchanges, and how the users treat crypto like an asset instead of a currency. When crypto is traded like an asset, it is bought and sold based on its perceived value. When crypto is treated like a currency, it is used to purchase everyday goods and services. &lt;/p&gt;

&lt;p&gt;But Stablecoin is different. Although it uses the blockchain to verify transactions like all cryptocurrency, its value is pegged to both gold and fiat currency. This allows stablecoin to be “stable” and not experience sudden fluctuations in its value. This stability makes it favorable to be used as a currency in transactions such as payments and payouts. Rapyd now offers stablecoin payouts, allowing you to pay others using cryptocurrency.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoin Payout Features
&lt;/h2&gt;

&lt;p&gt;When logging into the Client Portal, you can navigate to Disburse &amp;gt; Payouts and click the Create Payout button in the upper right corner. From there, you can create a payout, selecting UDSC as the payout currency. &lt;/p&gt;

&lt;p&gt;When creating a payout, you can select the crypto account that has the stablecoin. And then define the beneficiary that will receive the payout. Fiat currency can also be converted to stablecoin when making the payout. The beneficiary can either be an individual or a company. You also need to agree to the provided Terms and Conditions before completing the crypto payout. &lt;/p&gt;

&lt;p&gt;See &lt;a href="https://docs.rapyd.net/en/creating-a-crypto-payout.html" rel="noopener noreferrer"&gt;Creating a Crypto Payout&lt;/a&gt; for more detailed information.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Rapyd and Using Stablecoin
&lt;/h2&gt;

&lt;p&gt;Rapyd now offers stablecoin payouts through the Client Portal, and through the Rapyd API using the &lt;a href="https://docs.rapyd.net/en/create-payout.html" rel="noopener noreferrer"&gt;Create Payout&lt;/a&gt; endpoint. See &lt;a href="https://docs.rapyd.net/en/stablecoin-payout.html" rel="noopener noreferrer"&gt;Stablecoin Payout&lt;/a&gt; for more detailed information about how to complete a stablecoin payout using the API. Using stablecoins is useful because it gives you another financial tool that you can use. Both USDC and USDT are offered as payout currencies, and are now supported currencies. &lt;/p&gt;

&lt;p&gt;Fintech and stablecoin blend together perfectly because both represent moving outside of the traditional financial establishment and represent the efforts to try and create a better path for all parties involved. &lt;/p&gt;

&lt;p&gt;When using the Rapyd Client Portal, you can have a chance to enter the cryptocurrency world using a no-code solution. The process of using stablecoin is simplified. Rapyd provides the best available options to broaden your financial horizons.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>cryptocurrency</category>
      <category>stablecoin</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Client Portal: The No-Code Solution to Your Financial Needs</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Thu, 02 Oct 2025 18:46:43 +0000</pubDate>
      <link>https://dev.to/rapyd/client-portal-the-no-code-solution-to-your-financial-needs-mmm</link>
      <guid>https://dev.to/rapyd/client-portal-the-no-code-solution-to-your-financial-needs-mmm</guid>
      <description>&lt;p&gt;The Rapyd Client Portal is a self-service product that allows developers and merchants to manage the financial information and services that Rapyd provides. Users can quickly view the information they need, generate a useful report, or create a payment. The Client Portal turns complex financial operations into simple tasks. &lt;/p&gt;

&lt;h2&gt;
  
  
  No-Code Solutions
&lt;/h2&gt;

&lt;p&gt;Rapyd offers a no-code solution to our customers through the Client Portal. The portal is a point-and-click interface with various fields where important information can be entered. Files can be uploaded and downloaded. The Client Portal is a key component for merchants who wish to integrate with Rapyd. The KYB or Know Your Business Form is a required form used to verify a new business working with Rapyd. This form can be completed from the Client Portal. &lt;/p&gt;

&lt;p&gt;For developers that wish to integrate with Rapyd via code, they can use the Rapyd API. But the Client Portal is also important to these developers. Your API access key and secret key are securely stored in your Client Portal account, and are needed to make calls via the Rapyd API. &lt;/p&gt;

&lt;h2&gt;
  
  
  Client Portal Features
&lt;/h2&gt;

&lt;p&gt;The Client Portal has a wide range of features that allows users to interact with Rapyd’s core product offerings: Collect, Disburse, Issuing, and Rapyd Wallets. The first set of features center around viewing financial information. For example, users can view their payments, payouts, or a list of issued cards. Each line item in those lists can be expanded to provide additional details and financial information. The second set of features center around performing financial operations. This can include creating a payment, creating a payment link, creating a payout, creating a beneficiary, issuing a card, or creating a Rapyd Wallet. The third set of features center around file downloads, file uploads, and generating financial reports. &lt;/p&gt;

&lt;p&gt;Developers can manage their API Keys, whitelist IP addresses, and manage their webhooks in the sandbox environment under the &lt;strong&gt;Developers&lt;/strong&gt; section of the Client Portal. Users can also rotate their API keys as an added security measure. Other tools are available to use via the Client Portal such as Rapyd Protect. You can use machine learning to flag and block fraudulent transactions. &lt;/p&gt;

&lt;h2&gt;
  
  
  How Rapyd Can Help You
&lt;/h2&gt;

&lt;p&gt;The Rapyd Client Portal can meet your financial needs because it simplifies the complex. It allows the user to quickly find needed financial information, and streamline tasks like initiating a cross-border payout. The portal provides a central place for all of your financial information, and allows you to manage linked accounts, card disputes, and generate hosted pages to name a few examples. The accurate financial reports allow you to plan for a successful financial future and identify patterns that can be used to boost your business performance. Even repetitive tasks like managing subscriptions become easy when you use the Client Portal. You can create an account &lt;a href="https://dashboard.rapyd.net/sign-up" rel="noopener noreferrer"&gt;here&lt;/a&gt; and begin by experimenting in the sandbox environment. The Client Portal represents an opportunity to create a better financial future.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>nocode</category>
      <category>tutorial</category>
      <category>finance</category>
    </item>
    <item>
      <title>Fintech and SWIFT: How Fintech and Traditional Finance Work Together</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Fri, 19 Sep 2025 15:43:59 +0000</pubDate>
      <link>https://dev.to/rapyd/fintech-and-swift-how-fintech-and-traditional-finance-work-together-3jk</link>
      <guid>https://dev.to/rapyd/fintech-and-swift-how-fintech-and-traditional-finance-work-together-3jk</guid>
      <description>&lt;p&gt;SWIFT is a facet of the traditional financial system, facilitating international payments and payouts. The SWIFT network is most often used by a wide range of organizations, from large banks and corporations to small businesses. &lt;/p&gt;

&lt;p&gt;Fintech is a newer addition to the financial world. Fintech companies often build their own financial network that provide similar services. This is an effort to help reduce the red tape involved and improve the settlement times for transactions. &lt;/p&gt;

&lt;p&gt;But sometimes, these two different systems need to work together to take advantage of their strengths. The traditional financial systems usually have a farther reach, and have a larger number of users. Fintech companies have unique product offerings that are tailored to their customer base, and are often more lean and efficient when compared to traditional finance. &lt;/p&gt;

&lt;p&gt;When fintech networks integrate with the larger traditional financial system, it can allow a wider base of customers to create payments and payouts that have the widest reach and the fastest settlements with the least amount of compliance issues. The two types of financial networks can work together to form a greater whole.  &lt;/p&gt;

&lt;h2&gt;
  
  
  What is SWIFT?
&lt;/h2&gt;

&lt;p&gt;SWIFT or the Society for Worldwide Interbank Financial Telecommunication is an important element of the traditional financial system. SWIFT is a network of financial institutions that facilitate cross-border payments and disbursements. It is the main system used for international payments and payouts between countries.&lt;/p&gt;

&lt;p&gt;An important data standard, called ISO 20022, is used to standardize financial information that is used for a transaction. This enables a uniform system that can be used by financial institutions across the globe. Another important element of the SWIFT system is the BIC SWIFT code. The BIC (Business Identifier Code) is used to identify financial institutions and businesses when making a transaction. Including the BIC in the payment or payout request ensures that the transaction is routed to the correct beneficiary bank or beneficiary business.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Rapyd and SWIFT
&lt;/h2&gt;

&lt;p&gt;Rapyd offers a payout method type through SWIFT using the Rapyd API. You can call the Rapyd API to use the &lt;code&gt;xx_swift_bank&lt;/code&gt; payout method and create a cross-border payout using SWIFT. &lt;/p&gt;

&lt;p&gt;The user documentation for &lt;a href="https://docs.rapyd.net/en/swift-payout.html" rel="noopener noreferrer"&gt;SWIFT payouts&lt;/a&gt; includes information about regex, workflows, supported sender currencies, and required fields by country that are needed to create a SWIFT payout. Examples of a SWIFT payout are included. Additional information about purpose codes are included. Purpose codes are required for specific countries.    &lt;/p&gt;

&lt;h2&gt;
  
  
  Fintech and Traditional Finance Working Together
&lt;/h2&gt;

&lt;p&gt;Fintech and traditional finance are like two sides of the same coin. Both systems share some similarities and differences. When fintech becomes integrated with traditional financial systems, this allows you to experience the greatest benefits by having increased options, decreased settlements times, and a secure, reliable way to disburse funds across the globe. When fintech services become integrated with traditional financial services, a unified financial network is created that can best meet your financial needs. &lt;/p&gt;

</description>
      <category>fintech</category>
      <category>payments</category>
      <category>tutorial</category>
      <category>finance</category>
    </item>
    <item>
      <title>The Rapyd Partnership Program: What Rapyd Brings to Partners</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Fri, 12 Sep 2025 20:50:21 +0000</pubDate>
      <link>https://dev.to/rapyd/the-rapyd-partnership-program-what-rapyd-brings-to-partners-4800</link>
      <guid>https://dev.to/rapyd/the-rapyd-partnership-program-what-rapyd-brings-to-partners-4800</guid>
      <description>&lt;p&gt;Rapyd offers a unique opportunity through the Partnership Program. In this program, partners can work with Rapyd and gain access to the Rapyd API, the Partner Portal and other features aimed to smooth their operational flows, enable better tracking of merchants and transactions, and a system to manage their financial information with ease. &lt;/p&gt;

&lt;p&gt;Rapyd stands apart from the competition by providing an experience that is tailored to partners. For example, Rapyd provides a faster onboarding experience for partners. The &lt;a href="https://docs.rapyd.net/en/partner-api-reference.html" rel="noopener noreferrer"&gt;Partner API Reference&lt;/a&gt; documents how partners can manage their KYB applications via the API. Partners can view account information such as offerings and organizations by making an API call. &lt;/p&gt;

&lt;p&gt;Rapyd provides the Partner Portal, a self service option that allows partners to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add and invite merchants&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create custom pricing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manage sign-up links for your merchants&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Rapyd Partnership Program
&lt;/h2&gt;

&lt;p&gt;The Rapyd Partnership Program serves three different partner types: Independent Sales Organizations (ISOs), Payment Facilitators (PayFacs) and Referral Partners. &lt;/p&gt;

&lt;p&gt;A partner can sign an agreement with Rapyd, and then begin the integration and onboarding process. Rapyd is an ideal solution for partners. We can provide the services and financial infrastructure that partners need.  &lt;/p&gt;

&lt;p&gt;Rapyd provides extensive support to each partner that works with Rapyd. Our support teams ensure that each partner has a smooth experience and quick resolution times to any problems that could arise.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Partnership Types
&lt;/h2&gt;

&lt;p&gt;Here is some additional information about each partner type:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent Sales Organization&lt;/strong&gt; - An Independent Sales Organization (ISO) helps to contract and manage payment services for their merchants. ISOs directly interact with payment service providers like Rapyd to help manage the financial needs of their merchants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment Facilitator&lt;/strong&gt; - Payment Facilitators (PayFacs) help to facilitate payments for their merchants. They provide a specific range of services. PayFacs partner with payment service providers like Rapyd to contract and settle on behalf of their merchants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referral Partner&lt;/strong&gt; - Referral partners are payment experts who refer merchants to a payment service provider, like Rapyd. Each merchant adheres to the agreement with the payment service provider, so the Referral Partner doesn’t need to manage the merchants directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Rapyd Brings to Partners
&lt;/h2&gt;

&lt;p&gt;Join the Rapyd Certified Partner Program and reap the benefits of our partnership expertise. Rapyd supports Independent Sales Organization (ISO), Payment Facilitator (PayFac) and Referral Partners.&lt;/p&gt;

&lt;p&gt;Rapyd provides an array of support and benefits for partners that only get better as their business with Rapyd grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ISOs and Referral Partners&lt;/strong&gt; can utilize the Rapyd API and the Partner Portal. The Partner Portal is a self-service option that enables the partner to manage their merchants, signup links, and pricing. Partners can easily onboard merchants and link them to their account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PayFacs&lt;/strong&gt; are supported via the Rapyd API. They can create card payments, APM payments, and create payouts with Rapyd.&lt;/p&gt;

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

&lt;p&gt;The Rapyd Certified Partner Program is an excellent opportunity for partners to integrate with Rapyd. Rapyd provides the best solution for partners, including support via the Partner Portal and the Rapyd API. &lt;/p&gt;

&lt;p&gt;Rapyd helps partners to manage their merchants, track financial information, and offer custom pricing structures, in addition to signup links. Such features are financial tools that partners can use to build a better financial future.&lt;/p&gt;

</description>
      <category>iso</category>
      <category>payfac</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Multi-Currency Payment Systems: Using Rapyd’s Global Payment APIs</title>
      <dc:creator>mcduffin</dc:creator>
      <pubDate>Fri, 12 Sep 2025 20:34:53 +0000</pubDate>
      <link>https://dev.to/rapyd/multi-currency-payment-systems-using-rapyds-global-payment-apis-al9</link>
      <guid>https://dev.to/rapyd/multi-currency-payment-systems-using-rapyds-global-payment-apis-al9</guid>
      <description>&lt;p&gt;A UK-based SaaS company wants to pay its freelance designer in Brazil. They invoice in GBP, but she wants BRL in her account. Here’s how Rapyd makes that happen in seconds, not weeks.&lt;/p&gt;

&lt;p&gt;Multi-currency payment systems are crucial for creating cross-border solutions and integrating into a globalized financial network. Creating payments where funds are collected, or creating payouts where funds are disbursed is a staple feature of Rapyd’s core offering. The ability to use multiple currencies through FX or Foreign Exchange allows merchants across the globe to use a solution that meets their financial needs. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Multi-Curency Payment Systems?
&lt;/h2&gt;

&lt;p&gt;A multi-currency payment system is a network comprising several elements including payment processors, payment gateways, forex exchanges, payment service providers, and banks. All of these elements form a network to facilitate payments that use multiple different currencies. &lt;/p&gt;

&lt;p&gt;The key features of a multi-currency payment system include the ability to use different currencies in the same payment or payout. For example, a merchant could create a payout and select USD as the sender currency. But their employee lives in France, and would like to be paid in EUR. So the beneficiary currency would be EUR, and the funds would be converted into EUR via FX or Foreign Exchange. &lt;/p&gt;

&lt;p&gt;Payment systems that allow multiple currencies to be used allow a truly globalized system to form. The benefits of using multiple currencies include customers using their local currency, ease of use for cross-border transactions, and using payment and payout methods that are faster and more efficient. &lt;/p&gt;

&lt;h2&gt;
  
  
  Foreign Exchange (FX) in Fintech
&lt;/h2&gt;

&lt;p&gt;Foreign exchange (FX) is the act of converting one currency to another during a transaction. A payment gateway is used in the conversion process. The latest currency conversions and rates are used. &lt;/p&gt;

&lt;p&gt;The fintech industry has helped to revolutionize FX by offering real-time transactions that use different currencies. The overall process has less red tape, and is easier to use when compared to using a more traditional bank or financial institution. &lt;/p&gt;

&lt;p&gt;The ability to make settlements using your preferred currency is beneficial, especially when using local bank accounts and banking institutions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Global Payment APIs
&lt;/h2&gt;

&lt;p&gt;Rapyd offers a flexible and easy to use solution through our global payment APIs. The two main API calls used are &lt;a href="https://docs.rapyd.net/en/create-payment.html" rel="noopener noreferrer"&gt;Create Payment&lt;/a&gt; and &lt;a href="https://docs.rapyd.net/en/create-payout.html" rel="noopener noreferrer"&gt;Create Payout&lt;/a&gt;. Creating payments is under the Collect product offering, while creating payouts is under the Disburse product offering. &lt;/p&gt;

&lt;p&gt;Additional elements include a variety of &lt;a href="https://docs.rapyd.net/en/payment-method-types.html" rel="noopener noreferrer"&gt;payment method types&lt;/a&gt;, &lt;a href="https://docs.rapyd.net/en/payout-method-types.html" rel="noopener noreferrer"&gt;payout method types&lt;/a&gt;, and &lt;a href="https://docs.rapyd.net/en/supported-currencies.html" rel="noopener noreferrer"&gt;supported currencies&lt;/a&gt;. Rapyd offers a wide variety of supported currencies for payments, payouts, settlements, and virtual accounts. &lt;/p&gt;

&lt;p&gt;For example, your business based in Europe sells merchandise on your online store. A customer from the United States wants to purchase a product from you that costs 370 EUR (Euros). But the customer uses a payment method that only supports USD (U.S. Dollars). You would &lt;a href="https://docs.rapyd.net/en/payment-with-fx.html" rel="noopener noreferrer"&gt;Create a Payment with FX&lt;/a&gt; using the Rapyd API.&lt;/p&gt;

&lt;p&gt;You can use hundreds of different payment method types with Rapyd, which include ewallets, bank transfers, bank redirects, cash payments, and card payments. Supported regions include the Americas, APAC, and EMEA. You can use your preferred payment method types with the Rapyd API. &lt;/p&gt;

&lt;p&gt;Multi-currency payment systems are a key component of globalized financial networks. They allow the merchant to use multiple different currencies for both payments and payouts. This kind of system can offer the flexibility and ease of use for merchants that operate on a global scale and need cross-border financial solutions. Rapyd is a leader in providing multi-currency payment systems accessible through the Rapyd API. Learn &lt;a href="https://docs.rapyd.net/en/index-en.html" rel="noopener noreferrer"&gt;more&lt;/a&gt; about Rapyd and integrate with us today. Leave a comment below about your experience using our APIs. &lt;/p&gt;

</description>
      <category>api</category>
      <category>fintech</category>
      <category>multicurrency</category>
      <category>payments</category>
    </item>
    <item>
      <title>Blockchain Interoperability: Connecting Multiple Blockchain Networks for Payments</title>
      <dc:creator>Drew Harris</dc:creator>
      <pubDate>Tue, 09 Sep 2025 17:46:17 +0000</pubDate>
      <link>https://dev.to/rapyd/blockchain-interoperability-connecting-multiple-blockchain-networks-for-payments-35hn</link>
      <guid>https://dev.to/rapyd/blockchain-interoperability-connecting-multiple-blockchain-networks-for-payments-35hn</guid>
      <description>&lt;p&gt;Writer Name: &lt;strong&gt;Vivek Kumar Maskara&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blockchain is a decentralized and immutable digital database or ledger that's distributed across a network of computers in a transparent and tamperproof manner.&lt;/p&gt;

&lt;p&gt;Blockchain interoperability refers to the ability of blockchains to communicate and share data with other blockchains. This enables developers to build cross-chain solutions that combine the strengths of each blockchain. For example, financial applications need blockchain interoperability to enable smooth cross-chain transactions and to improve liquidity, accessibility, and expand financial inclusion.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to implement blockchain interoperability using &lt;a href="https://soliditylang.org/" rel="noopener noreferrer"&gt;Solidity&lt;/a&gt; smart contracts for payment verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Fintech Applications Need Blockchain Interoperability
&lt;/h2&gt;

&lt;p&gt;If you're building a &lt;a href="https://www.coinbase.com/en-ca/learn/crypto-basics/what-are-decentralized-applications-dapps" rel="noopener noreferrer"&gt;decentralized application&lt;/a&gt; (dApps), you need blockchain interoperability. This allows your app to interact with different blockchain networks, expanding the app's access to assets, usability, and scalability.&lt;/p&gt;

&lt;p&gt;Here are a few benefits of blockchain interoperability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seamless cross-chain transactions:&lt;/strong&gt; Blockchain interoperability enables users to transfer assets across different blockchain networks without relying on centralized exchanges or manually converting them to &lt;a href="https://www.trmlabs.com/glossary/fiat-currency" rel="noopener noreferrer"&gt;fiat currency&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower transaction costs and faster settlement:&lt;/strong&gt; It reduces the need for intermediaries to carry out a transaction, optimizes payment processing costs, and enables &lt;a href="https://www.rapidinnovation.io/post/blockchain-for-cross-border-payments-ultimate-guide-to-fast-cheap-transfers#42-faster-settlement-times" rel="noopener noreferrer"&gt;faster settlement across blockchain networks&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved liquidity and accessibility:&lt;/strong&gt; It connects fragmented payment ecosystems, allowing users to access a wider liquidity pool to exchange their tokens. For example, a user can transfer USDT from Ethereum (&lt;a href="https://ethereum.org/en/developers/docs/standards/tokens/erc-20/" rel="noopener noreferrer"&gt;ERC-20&lt;/a&gt;) to the &lt;a href="https://tron.network/" rel="noopener noreferrer"&gt;Tron network&lt;/a&gt; to take advantage of lower fees and faster transaction speeds. This enhances financial accessibility for global users as they can transfer their assets to a different chain based on their needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expanded financial inclusion:&lt;/strong&gt; Supporting multichain compatibility enables fintech applications to reach more users and businesses, even in regions where traditional financial services are limited.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Simplifying Payment Verification with Rapyd
&lt;/h2&gt;

&lt;p&gt;Blockchain interoperability sounds great in theory: move assets and data across networks seamlessly. But building a complete payment verification system requires significant developer effort. Smart contracts struggle to handle real-world requirements, like fiat conversions, regulatory compliance, fraud prevention, and off-chain settlement on their own, especially when these systems must remain reliable and responsive at scale.&lt;/p&gt;

&lt;p&gt;Thankfully, &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; offers a robust financial infrastructure with easy-to-integrate &lt;a href="https://docs.rapyd.net/en/get-started.html" rel="noopener noreferrer"&gt;REST APIs&lt;/a&gt; that offload the hard parts for you. It acts as an off-chain settlement layer and cross-chain data exchange, enabling use cases like automated compliance checks, &lt;a href="https://docs.rapyd.net/en/managing-escrow.html" rel="noopener noreferrer"&gt;escrow&lt;/a&gt;, and transaction verification, without requiring developers to build and maintain that infrastructure themselves.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://www.rapyd.net/blog/ai-fraud-prevention/" rel="noopener noreferrer"&gt;real-time fraud prevention&lt;/a&gt; and automated &lt;a href="https://www.rapyd.net/blog/how-rapyd-verify-can-help-global-businesses-with-robust-kyc/" rel="noopener noreferrer"&gt;global KYC/AML&lt;/a&gt; to fiat on/off-ramps in over 100 countries, Rapyd removes the traditional finance complexity from blockchain payments. The &lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;Rapyd API platform&lt;/a&gt; simplifies the end-to-end financial workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;&lt;a href="https://docs.rapyd.net/en/rapyd-collect-363484.html" rel="noopener noreferrer"&gt;Collect Payments API&lt;/a&gt;&lt;/strong&gt; receives funds from various payment methods and deposits them into &lt;a href="https://docs.rapyd.net/en/rapyd-wallet.html" rel="noopener noreferrer"&gt;Rapyd Wallets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;a href="https://docs.rapyd.net/en/rapyd-verify-386565.html" rel="noopener noreferrer"&gt;Rapyd Verify APIs&lt;/a&gt;&lt;/strong&gt; perform hosted Know Your Business (KYB) and Know Your Customer (KYC) verifications to meet compliance requirements.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;a href="https://docs.rapyd.net/en/rapyd-protect.html" rel="noopener noreferrer"&gt;Rapyd Protect APIs&lt;/a&gt;&lt;/strong&gt; detect fraud and suspicious activity through built-in risk scoring, enhancing compliance and eliminating the need to build custom fraud logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.rapyd.net/en/webhook-format.html" rel="noopener noreferrer"&gt;Webhooks&lt;/a&gt;&lt;/strong&gt; receive real-time notifications for key events, such as payment status changes or verification updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an off-chain infrastructure layer, Rapyd also aligns with one of the core principles of blockchain systems: availability. Even during peak congestion or transaction delays on the blockchain, Rapyd continues to operate reliably, ensuring high availability for compliance, verification, and settlement workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Blockchain Interoperability
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you'll build, deploy, and test smart contracts on Ethereum testnets like &lt;a href="https://sepolia.etherscan.io/" rel="noopener noreferrer"&gt;Sepolia&lt;/a&gt; and &lt;a href="https://holesky.etherscan.io/" rel="noopener noreferrer"&gt;Holesky&lt;/a&gt;. The tutorial uses the &lt;a href="https://hardhat.org/hardhat-runner/docs/getting-started#overview" rel="noopener noreferrer"&gt;Hardhat&lt;/a&gt; developer environment to easily deploy your &lt;a href="https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html" rel="noopener noreferrer"&gt;Solidity smart contracts&lt;/a&gt; and test them using the Hardhat network, which is a local Ethereum network designed for development.&lt;/p&gt;

&lt;p&gt;Here are the primary components of the project:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F3270xrx1889xlkub4wnk.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.amazonaws.com%2Fuploads%2Farticles%2F3270xrx1889xlkub4wnk.png" alt="Project architecture, courtesy of Vivek Kumar Maskara" width="800" height="1738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CrossChainPayment&lt;/code&gt;&lt;/strong&gt; is a simple smart contract that initiates a cross-chain transaction and emits the &lt;code&gt;PaymentSent&lt;/code&gt; event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PaymentVerifier&lt;/code&gt;&lt;/strong&gt; is another smart contract that simulates a cross-chain transaction verification and emits the &lt;code&gt;PaymentVerified&lt;/code&gt; event. For simplicity, it doesn't perform any complex checks, but it can be extended based on the use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;relay.js&lt;/code&gt; script&lt;/strong&gt; watches for events on the source chain (&lt;em&gt;ie&lt;/em&gt; Sepolia) with the destination chain as Holesky and invokes the &lt;code&gt;verifyPayment&lt;/code&gt; method of the destination chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you get started, you need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/learn/getting-started/introduction-to-nodejs" rel="noopener noreferrer"&gt;Install a Node.js development environment&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Sign up for an &lt;a href="https://auth.alchemy.com/signup/" rel="noopener noreferrer"&gt;Alchemy account&lt;/a&gt; and &lt;a href="https://docs.alchemy.com/docs/alchemy-quickstart-guide#1key-create-an-alchemy-api-key" rel="noopener noreferrer"&gt;create an API key&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.metamask.io/start/getting-started-with-metamask/" rel="noopener noreferrer"&gt;Set up Metamask and create a wallet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/" rel="noopener noreferrer"&gt;Obtain a Metamask account private key&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Fund your Metamask wallet using Google Cloud's Ethereum &lt;a href="https://cloud.google.com/application/web3/faucet/ethereum/holesky" rel="noopener noreferrer"&gt;Holešky&lt;/a&gt; and &lt;a href="https://cloud.google.com/application/web3/faucet/ethereum/sepolia" rel="noopener noreferrer"&gt;Sepolia Faucet&lt;/a&gt;. You can use any faucet of your choice, but some faucets may require a minimum wallet balance to receive funds. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cloning the Starter Project
&lt;/h3&gt;

&lt;p&gt;This tutorial uses &lt;a href="https://github.com/Rapyd-Samples/rapyd-starter-blockhain" rel="noopener noreferrer"&gt;this GitHub starter code&lt;/a&gt; that has a barebones Hardhat app set up. To follow along, clone the GitHub repo and switch to the &lt;code&gt;starter&lt;/code&gt; branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Rapyd-Samples/rapyd-starter-blockhain.git
&lt;span class="nb"&gt;cd &lt;/span&gt;rapyd-starter-blockhain
git checkout starter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's go over the structure of the codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;package.json&lt;/code&gt; file defines the Hardhat project dependencies. It uses the &lt;a href="https://www.npmjs.com/package/hardhat" rel="noopener noreferrer"&gt;Hardhat&lt;/a&gt; dependency to get an Ethereum development environment and uses &lt;a href="https://www.npmjs.com/package/@nomicfoundation/hardhat-toolbox" rel="noopener noreferrer"&gt;@nomicfoundation/hardhat-toolbox&lt;/a&gt; to get all the common Hardhat plugins.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;contracts&lt;/code&gt; directory contains a sample &lt;code&gt;Lock.sol&lt;/code&gt; solidity smart contract. You won't need it in the tutorial, but you can use it to understand the basic structure of a Solidity smart contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Defining Smart Contracts for Cross-Chain Transaction Verification
&lt;/h3&gt;

&lt;p&gt;In this section, you'll define two smart contracts to enable cross-chain transaction verification. &lt;/p&gt;

&lt;h4&gt;
  
  
  CrossChainPayment contract
&lt;/h4&gt;

&lt;p&gt;Initially, you'll define the &lt;code&gt;CrossChainPayment&lt;/code&gt; that initiates a cross-chain payment and emits the &lt;code&gt;PaymentSent&lt;/code&gt; event. Here, you'll define a simple smart contract that checks for amount mismatch issues before emitting the &lt;code&gt;PaymentSent&lt;/code&gt; event. &lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;contracts/CrossChainPayment.sol&lt;/code&gt; file and add the following code snippet to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract CrossChainPayment {
    event PaymentSent(address indexed from, address indexed to, uint256 amount, uint256 chainId);

    function sendPayment(address to, uint256 amount, uint256 destChainId) external payable {
        require(msg.value == amount, "Amount mismatch");
        emit PaymentSent(msg.sender, to, amount, destChainId);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The smart contract defines a &lt;code&gt;sendPayment&lt;/code&gt; function that does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It takes the &lt;code&gt;to&lt;/code&gt; address and &lt;code&gt;amount&lt;/code&gt; for sending the payment, and it asserts that the &lt;code&gt;amount&lt;/code&gt; matches the value set by the sender in the &lt;code&gt;msg&lt;/code&gt; payload.&lt;/li&gt;
&lt;li&gt;It emits a &lt;code&gt;PaymentSent&lt;/code&gt; event with the sender's address (&lt;code&gt;msg.sender&lt;/code&gt;), receiver's address (&lt;code&gt;to&lt;/code&gt;), amount, and destination chain ID (&lt;code&gt;destChainId&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  PaymentVerified contract
&lt;/h4&gt;

&lt;p&gt;Now it's time to define the &lt;code&gt;PaymentVerifier&lt;/code&gt; that will be invoked by the &lt;a href="https://antiersolutions.medium.com/the-role-of-blockchain-relayer-in-transforming-financial-systems-ca2776dd761f" rel="noopener noreferrer"&gt;relayer&lt;/a&gt; (more on this later). The &lt;code&gt;PaymentVerifier&lt;/code&gt; smart contract contains the &lt;code&gt;verifyPayment&lt;/code&gt; method, where on-chain verification can be performed before emitting the &lt;code&gt;PaymentVerified&lt;/code&gt; event. &lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;contracts/PaymentVerifier.sol&lt;/code&gt; file and add the following code snippet to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract PaymentVerifier {
    event PaymentVerified(address indexed to, uint256 amount, uint256 srcChainId, bytes32 txHash);

    function verifyPayment(address to, uint256 amount, uint256 srcChainId, bytes32 txHash) external {
        // perform verification steps
        emit PaymentVerified(to, amount, srcChainId, txHash);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This contract doesn't perform a full cross-chain verification on its own. It acts as a receiver for cross-chain data, and usually, a relayer or a middleware service invokes the &lt;code&gt;verifyPayment&lt;/code&gt; method of the contract. This method expects the transaction receiver's address (&lt;code&gt;to&lt;/code&gt;), amount, source chain ID (&lt;code&gt;srcChainId&lt;/code&gt;), and the transaction hash (&lt;code&gt;txHash&lt;/code&gt;). These fields will be supplied by the relayer while invoking the contract. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;txHash&lt;/code&gt; refers to the on-chain transaction hash and can be used to perform additional transaction checks before emitting the &lt;code&gt;PaymentVerified&lt;/code&gt; event. For this tutorial, the smart contract emits the event without any checks.&lt;/p&gt;

&lt;p&gt;Note that in a real-world system, the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract can be extended to include more extensive verification checks, such as validating signatures and cryptographic proofs. &lt;/p&gt;

&lt;h3&gt;
  
  
  Defining Scripts to Deploy the Smart Contracts
&lt;/h3&gt;

&lt;p&gt;Now that you've defined the smart contracts, you need to deploy them to &lt;a href="https://www.alchemy.com/overviews/what-are-testnets" rel="noopener noreferrer"&gt;test blockchain networks (testnets)&lt;/a&gt; before you can use them. &lt;/p&gt;

&lt;p&gt;To deploy the smart contracts, you can use a &lt;code&gt;node&lt;/code&gt; script that uses the &lt;code&gt;hardhat&lt;/code&gt; SDK to deploy the smart contract and print its address. To deploy the &lt;code&gt;CrossChainPayment&lt;/code&gt; contract, create a &lt;code&gt;scripts/deploy_crosschain.js&lt;/code&gt; script and add the following contents to it:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat&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;main&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;Contract&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;hre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CrossChainPayment&lt;/span&gt;&lt;span class="dl"&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;contract&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;Contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contract deployed to:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&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;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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 Hardhat SDK creates an instance of the Ethers &lt;a href="https://docs.ethers.org/v5/api/contract/contract-factory/" rel="noopener noreferrer"&gt;ContractFactory&lt;/a&gt; to convert the &lt;code&gt;CrossChainPayment&lt;/code&gt; solidity contract into &lt;a href="https://blog.chain.link/what-are-abi-and-bytecode-in-solidity/" rel="noopener noreferrer"&gt;bytecode&lt;/a&gt; and deploys it on the blockchain network. &lt;/p&gt;

&lt;p&gt;Before using the script, you need to make sure that the network is configured under &lt;code&gt;hardhat.config.js&lt;/code&gt;. You also need to specify the network name as a CLI parameter while using the script. More on this in a later section. &lt;/p&gt;

&lt;p&gt;Similarly, to deploy the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract, create a &lt;code&gt;scripts/deploy_paymentverifier.js&lt;/code&gt; file and add the following contents to it:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hardhat&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;main&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;Contract&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;hre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PaymentVerifier&lt;/span&gt;&lt;span class="dl"&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;contract&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;Contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForDeployment&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contract deployed to:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAddress&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&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;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;exitCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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 script converts the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract into bytecode and deploys it. Notice that both the contract deployment scripts are quite similar, and you could also parameterize the script and pass the contract name as a CLI argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the Smart Contracts to testnets
&lt;/h3&gt;

&lt;p&gt;This tutorial uses the &lt;a href="https://sepolia.etherscan.io/" rel="noopener noreferrer"&gt;Sepolia&lt;/a&gt; and &lt;a href="https://holesky.etherscan.io/" rel="noopener noreferrer"&gt;Holesky&lt;/a&gt; testnets. Because deploying a smart contract to a &lt;a href="https://www.web3.university/tracks/create-a-smart-contract/what-is-gas-and-how-is-it-used" rel="noopener noreferrer"&gt;network requires gas&lt;/a&gt;, make sure to fund your wallet with tokens using a faucet like Google Cloud's Ethereum &lt;a href="https://cloud.google.com/application/web3/faucet/ethereum/holesky" rel="noopener noreferrer"&gt;Holešky&lt;/a&gt; or &lt;a href="https://cloud.google.com/application/web3/faucet/ethereum/sepolia" rel="noopener noreferrer"&gt;Sepolia Faucet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before deploying the smart contracts, you need to configure the desired testnets in the &lt;code&gt;hardhat.config.js&lt;/code&gt; file. Update the contents of the &lt;code&gt;hardhat.config.js&lt;/code&gt; file with the following code snippet:&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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nomicfoundation/hardhat-toolbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;solidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.8.20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sepolia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&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;SEPOLIA_RPC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&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;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11155111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;holesky&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&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;HOLESKY_RPC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accounts&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;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;17000&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;p&gt;This snippet updates the &lt;code&gt;network&lt;/code&gt; configuration to add &lt;code&gt;sepolia&lt;/code&gt; and &lt;code&gt;holesky&lt;/code&gt; testnets. Notice that the config file uses &lt;code&gt;SEPOLIA_RPC&lt;/code&gt;, &lt;code&gt;HOLESKY_RPC&lt;/code&gt;, and &lt;code&gt;PRIVATE_KEY&lt;/code&gt; environment variables. To configure environment variables, create a &lt;code&gt;.env&lt;/code&gt; file in the project's root and add the following contents to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRIVATE_KEY=&amp;lt;YOUR_META_MASK_PRIVATE_KEY&amp;gt;
SEPOLIA_RPC=&amp;lt;YOUR_ALCHEMY_SEPOLOIA_TEST_NET_HTTPS_RPC_URL&amp;gt;
SEPOLIA_WSS=&amp;lt;YOUR_ALCHEMY_SEPOLOIA_TEST_NET_WSS_RPC_URL&amp;gt;
HOLESKY_RPC=&amp;lt;YOUR_ALCHEMY_HOLESKY_TEST_NET_HTTPS_RPC_URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;YOUR_META_MASK_PRIVATE_KEY&amp;gt;&lt;/code&gt; with the &lt;a href="https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/" rel="noopener noreferrer"&gt;Metamask wallet's private key&lt;/a&gt; obtained earlier. Replace &lt;code&gt;&amp;lt;YOUR_ALCHEMY_SEPOLOIA_TEST_NET_HTTPS_RPC_URL&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;YOUR_ALCHEMY_HOLESKY_TEST_NET_HTTPS_RPC_URL&amp;gt;&lt;/code&gt; with your HTTPS RPC URL from the Alchemy dashboard:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F7jl1zdfouvcwo0snnmwe.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.amazonaws.com%2Fuploads%2Farticles%2F7jl1zdfouvcwo0snnmwe.png" alt="Obtain RPC URLs from Alchemy" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, make sure you replace &lt;code&gt;&amp;lt;YOUR_ALCHEMY_SEPOLOIA_TEST_NET_WSS_RPC_URL&amp;gt;&lt;/code&gt; with the WebSocket (WSS) RPC URL obtained from the Alchemy dashboard.&lt;/p&gt;

&lt;p&gt;Now that your environment variables and Hardhat configuration are set up, you can deploy the contracts. Either of these chains can be used as a source or destination for a transaction, so you need to deploy &lt;code&gt;PaymentVerifier&lt;/code&gt; and &lt;code&gt;CrossChainPayment&lt;/code&gt; contracts to both Sepolia and Holesky chains.&lt;/p&gt;

&lt;p&gt;Execute the following command to deploy the &lt;code&gt;CrossChainPayment&lt;/code&gt; to the Holesky chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy_crosschain.js &lt;span class="nt"&gt;--network&lt;/span&gt; holesky
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might take a few seconds for the contract to deploy, and it outputs the contract address 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;Contract deployed to: 0x09292e7C53697DFcdBA3c51425bb7e36d7F6Ef2a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the contract address and deploy the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract to the Holesky chain by executing the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy_paymentverifier.js &lt;span class="nt"&gt;--network&lt;/span&gt; holesky
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, deploy the &lt;code&gt;CrossChainPayment&lt;/code&gt; to the Sepolia chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy_crosschain.js &lt;span class="nt"&gt;--network&lt;/span&gt; sepolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, deploy the &lt;code&gt;PaymentVerifier&lt;/code&gt; to the Sepolia chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx hardhat run scripts/deploy_paymentverifier.js &lt;span class="nt"&gt;--network&lt;/span&gt; sepolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to save all the contract addresses as you will need them while defining the relay script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining the Relay Script
&lt;/h3&gt;

&lt;p&gt;Now that the smart contracts are defined, you need to define a relay script that will listen for &lt;code&gt;PaymentSent&lt;/code&gt; events on the source chain. When it finds a &lt;code&gt;PaymentSent&lt;/code&gt; event, it matches the destination chain ID and triggers the &lt;code&gt;verifyPayment&lt;/code&gt; smart contract method on the destination chain.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;scripts/relay.js&lt;/code&gt; file and add the following code snippet to it:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocketProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Wallet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Use WebSocket for Sepolia (where we're listening for events)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SEPOLIA_WSS&lt;/span&gt; &lt;span class="o"&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;SEPOLIA_WSS&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;HOLESKY_RPC&lt;/span&gt; &lt;span class="o"&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;HOLESKY_RPC&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;PRIVATE_KEY&lt;/span&gt; &lt;span class="o"&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;PRIVATE_KEY&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;SEPOLIA_CONTRACT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;YOUR_CROSS_PAYMENT_SEPOLIA_ADDRESS&amp;gt;&lt;/span&gt;&lt;span class="dl"&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;HOLESKY_VERIFIER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;YOUR_PAYMENT_VERIFIER_HOLESKY_ADDRESS&amp;gt;&lt;/span&gt;&lt;span class="dl"&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;eventAbi&lt;/span&gt; &lt;span class="o"&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;event PaymentSent(address indexed from, address indexed to, uint256 amount, uint256 chainId)&lt;/span&gt;&lt;span class="dl"&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;verifierAbi&lt;/span&gt; &lt;span class="o"&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;function verifyPayment(address to, uint256 amount, uint256 srcChainId, bytes32 txHash)&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;startRelayer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Use WebSocketProvider for event monitoring&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&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;WebSocketProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SEPOLIA_WSS&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;holeskyProvider&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;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HOLESKY_RPC&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;wallet&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;Wallet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PRIVATE_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;holeskyProvider&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;sourceContract&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;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SEPOLIA_CONTRACT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HOLESKY_VERIFIER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;verifierAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wallet&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Relayer is watching for events on Sepolia via WebSocket...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set up reconnection logic&lt;/span&gt;
    &lt;span class="nx"&gt;sepoliaProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&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;code&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="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;`WebSocket connection closed with code &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Reconnecting...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startRelayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Listen for events using WebSocket&lt;/span&gt;
    &lt;span class="nx"&gt;sourceContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PaymentSent&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="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="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chainId&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="o"&gt;=&amp;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PaymentSent Detected:&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="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="na"&gt;amount&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;chainId&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;chainId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;17000&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Skipping non-Holesky destination.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Get transaction hash from event and ensure it's in bytes32 format&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txHash&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;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionHash&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;tx&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;verifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyPayment&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="nc"&gt;BigInt&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="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                &lt;span class="mi"&gt;11155111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;txHash&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verification TX sent:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Error verifying payment:&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;message&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error details:&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle process termination&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&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="o"&gt;=&amp;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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Closing WebSocket connection...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sepoliaProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&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="nf"&gt;exit&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="c1"&gt;// In case of connection errors, restart the relayer&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;startRelayer&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;error&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;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;Error starting relayer:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startRelayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&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;To interact with the Sepolia and Holesky chains, this code creates an instance of the &lt;a href="https://docs.ethers.org/v5/api/providers/jsonrpc-provider/" rel="noopener noreferrer"&gt;JsonRpcProvider&lt;/a&gt;. The relay script assumes that the transaction will be initiated on the Sepolia chain and starts listening for the &lt;code&gt;PaymentSent&lt;/code&gt; events on this chain. It sets the Holesky chain as the destination chain, creates an instance of the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract deployed on it, and invokes the &lt;code&gt;verifyPayment&lt;/code&gt; method using the payload received in the &lt;code&gt;PaymentSent&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;This setup enables blockchain interoperability by bridging event data between two separate chains. It captures transactions on the source chain (Sepolia) and triggers verification logic on the destination chain (Holesky), without requiring a built-in bridge or shared state between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Cross-chain Interactions
&lt;/h3&gt;

&lt;p&gt;With smart contracts deployed to both Sepolia and Holesky chains and the relay scripts in place, you can test blockchain interoperability. The goal of testing is to verify the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initiate a cross-chain transaction on the Sepolia chain using the &lt;code&gt;sendPayment&lt;/code&gt; method defined in the &lt;code&gt;CrossChainPayment&lt;/code&gt;. Once the transaction is complete, you will receive a &lt;code&gt;ContractTransactionResponse&lt;/code&gt; confirming the successful completion of the transaction.&lt;/li&gt;
&lt;li&gt;Within a few seconds of completion, the relay script should receive a &lt;code&gt;PaymentSent&lt;/code&gt; event from the &lt;code&gt;CrossChainPayment&lt;/code&gt; contract deployed on the Sepolia chain. The script should invoke the &lt;code&gt;verifyPayment&lt;/code&gt; method defined in the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract deployed on the Holesky chain.&lt;/li&gt;
&lt;li&gt;On invocation, the &lt;code&gt;PaymentVerifier&lt;/code&gt; contract deployed on the Holesky chain should verify the transaction and return a successful response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you begin testing, start the relay script in a new terminal window:&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="c"&gt;# Execute this command in the project's root directory&lt;/span&gt;
node scripts/relay.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code starts the relay script, which listens for events on the Sepolia chain.&lt;/p&gt;

&lt;p&gt;To initiate a transaction on the Sepolia chain, start the &lt;code&gt;hardhat&lt;/code&gt; console for the &lt;code&gt;sepolia&lt;/code&gt; network by executing the following command in the project's root in a separate terminal window:&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="c"&gt;# Execute this in the project's root&lt;/span&gt;
npx hardhat console &lt;span class="nt"&gt;--network&lt;/span&gt; sepolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command starts the &lt;code&gt;hardhat&lt;/code&gt; console, where you can execute smart contracts and perform transactions. To initiate a cross-chain payment, paste the following script in the &lt;code&gt;hardhat&lt;/code&gt; console:&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;// Get ethers from Hardhat runtime&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;ethers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hre&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Load your deployed contract&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contract&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;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContractAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CrossChainPayment&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;0x45d88f6DD0f0eDB69C563233Be73458c9980b519&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Send payment&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x11ddd4b07B095802B537267358fB8Eb954B29d99&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&lt;/span&gt;
  &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                              &lt;span class="c1"&gt;// Amount&lt;/span&gt;
  &lt;span class="mi"&gt;17000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                                   &lt;span class="c1"&gt;// Destination Chain ID (Holešky)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ethers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseEther&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.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="c1"&gt;// Payment value&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that if the terminal prompts you to confirm pasting multiple lines of code, click &lt;strong&gt;Paste&lt;/strong&gt; to confirm the action. Once you paste the code and press &lt;strong&gt;Enter&lt;/strong&gt;, you will receive a &lt;code&gt;ContractTransactionResponse&lt;/code&gt; confirming that the transaction was successful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ContractTransactionResponse {
  provider: HardhatEthersProvider {
    _hardhatProvider: LazyInitializationProviderAdapter {
      _providerFactory: [AsyncFunction (anonymous)],
      _emitter: [EventEmitter],
      _initializingPromise: [Promise],
      provider: [BackwardsCompatibilityProviderAdapter]
    },
    _networkName: 'sepolia',
    _blockListeners: [],
    _transactionHashListeners: Map(0) {},
    _eventListeners: []
  },
  blockNumber: null,
  blockHash: null,
  index: undefined,
  hash: '0xa2bd160eb0bfde7b1eadbde6c3a1c3a19f876ed5e8731e0d32d582628ba5e361',
  type: 2,
  to: '0x45d88f6DD0f0eDB69C563233Be73458c9980b519',
  from: '0xcD0AAcf118B43C0878D90886f0e1D54D043CF726',
  nonce: 7,
  gasLimit: 25215n,
  gasPrice: 55068367n,
  maxPriorityFeePerGas: 50000000n,
  maxFeePerGas: 55068367n,
  maxFeePerBlobGas: null,
  data: '0x8d82e72f00000000000000000000000011ddd4b07b095802b537267358fb8eb954b29d9900000000000000000000000000000000000000000000000000038d7ea4c680000000000000000000000000000000000000000000000000000000000000004268',
  value: 1000000000000000n,
  chainId: 11155111n,
  signature: Signature { r: "0x9c0bc7dd319671a3bf13c09e3d0b7398529fe0805055a86ecb41fc7a0a2c76f8", s: "0x047fcab2b6594f02d3fa8b3d0a00e92364b8c1a41713126cd2974bc15d00dd52", yParity: 0, networkV: null },
  accessList: [],
  blobVersionedHashes: null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response contains details about the executed transaction, including the transaction hash (&lt;code&gt;hash&lt;/code&gt;), the sender's address (&lt;code&gt;from&lt;/code&gt;), the receiver's address (&lt;code&gt;to&lt;/code&gt;), and the hashed transaction payload (&lt;code&gt;data&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Head back to the relay script's terminal window, and within a few seconds, you should see a &lt;code&gt;PaymentSent&lt;/code&gt; event log printed in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentSent Detected:
{
  from: '0xcD0AAcf118B43C0878D90886f0e1D54D043CF726',
  to: '0x11ddd4b07B095802B537267358fB8Eb954B29d99',
  amount: '1000000000000000',
  chainId: 17000n
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; addresses in the event match the addresses received in the &lt;code&gt;ContractTransactionResponse&lt;/code&gt;. The relay script will invoke the &lt;code&gt;verifyPayment&lt;/code&gt; contract on the Holesky chain, and within a few seconds, a transaction verification log will be printed in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verification TX sent: 0xb034b9bcd67eb3b58615fcdcd3704df70f75608bda16c1f2a454dc0f200a14dd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this tutorial, you learned how to achieve blockchain interoperability by building smart contracts using Solidity and Hardhat. Smart contracts can be used to initiate cross-chain transactions and verify them based on preset rules. This lets developers connect and share data between isolated blockchains, helping them build innovative applications.&lt;/p&gt;

&lt;p&gt;However, developing extensive verification checks to handle different scenarios relating to fraud, settlement, and compliance can be challenging for developers. With &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt;, developers can focus on their core application logic, whether on-chain or off-chain, while relying on Rapyd to handle the complexity of compliance, identity verification, and secure settlement. More than just a complementary tool, Rapyd serves as your fiat bridge and compliance co-pilot by simplifying off-chain infrastructure, allowing teams to focus on delivering innovative, seamless, and scalable payment experiences faster. &lt;/p&gt;

&lt;p&gt;Experiment with the &lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;Rapyd API&lt;/a&gt; and the &lt;a href="https://github.com/Rapyd-Samples/rapyd-starter-blockhain" rel="noopener noreferrer"&gt;code used in this tutorial&lt;/a&gt; to discover how Rapyd can offer you the best in building an interoperable payment system.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creator Economy: How to Manage Royalties, Payouts, and Subscriptions with Rapyd</title>
      <dc:creator>Drew Harris</dc:creator>
      <pubDate>Tue, 08 Jul 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/rapyd/creator-economy-how-to-manage-royalties-payouts-and-subscriptions-with-rapyd-test-3i81</link>
      <guid>https://dev.to/rapyd/creator-economy-how-to-manage-royalties-payouts-and-subscriptions-with-rapyd-test-3i81</guid>
      <description>&lt;p&gt;By: &lt;strong&gt;Gourav Bais&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The creator economy is all about the creation, collection, distribution, and monetization of content, skills, or products. Today's creators—whether YouTubers, streamers, educators, or musicians—are entrepreneurs. And behind every piece of digital content, there's usually an overlooked task: managing payments.&lt;/p&gt;

&lt;p&gt;Creators have to juggle multiple income streams, including ad revenue, brand deals, subscriptions, affiliate commissions, digital product sales, brand partnerships, and direct fan contributions. Managing this mix means dealing with royalties, fluctuating transaction fees, and cross-border payouts. Creators need a payment system that simplifies operations, supports global subscriptions, and makes it easy to pay collaborators and affiliates.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; can help creators manage payments, receive international transactions, and simplify financial operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Payment Requirements in the Creator Economy
&lt;/h2&gt;

&lt;p&gt;Managing payments isn't just about moving money from point A to B; it's also about how the transfer happens: the speed, cost, currency, compliance, and overall user experience. Let's take a look at the core payment requirements that define how financial transactions work in the creator economy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Royalty Payments
&lt;/h3&gt;

&lt;p&gt;Royalty payments can be complicated for creators collaborating on podcasts, music, or shared ad revenue. One of the biggest challenges is tracking revenue sources across platforms and ensuring everyone gets their fair share. Delays or errors in royalty distribution can damage trust and often lead to disputes when multiple parties are involved across various locations.&lt;/p&gt;

&lt;p&gt;Clear, reliable royalty payments are key to maintaining long-term partnerships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Affiliate Payouts
&lt;/h3&gt;

&lt;p&gt;Affiliate marketing allows influencers and creators to earn commissions by promoting products or services—but managing multiple affiliates with varying rates, performance metrics, and payout schedules is tricky. Tracking earnings and calculating variable commissions based on clicks, sales, and engagement can also become increasingly difficult at scale.&lt;/p&gt;

&lt;p&gt;To keep up, creators need tools that can not only automate payment calculations but also scale seamlessly as the affiliate networks expand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscription Management
&lt;/h3&gt;

&lt;p&gt;Subscriptions provide a steady stream of income and build a loyal community. But handling subscription-based businesses is often challenging, especially when it comes to handling recurring payments and renewals across different regions and currencies.&lt;/p&gt;

&lt;p&gt;Subscriptions can also have multiple tiers, and each tier can have its own pricing, benefits, and audience. Managing these tiers requires a flexible system that can accommodate upgrades, downgrades, cancellations, and trial periods without disrupting the user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of Using Rapyd APIs for Creator Payment Systems
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.rapyd.net/en/api-reference.html" rel="noopener noreferrer"&gt;Rapyd's suite of APIs&lt;/a&gt; provides the necessary infrastructure to scale payments efficiently, securely, and globally. It simplifies complex creator payment workflows by providing the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplified integration for complex payment flows:&lt;/strong&gt; With a few lines of code, developers can integrate &lt;a href="https://www.rapyd.net/company/partners/" rel="noopener noreferrer"&gt;revenue sharing&lt;/a&gt;, &lt;a href="https://docs.rapyd.net/en/create-payout.html" rel="noopener noreferrer"&gt;automate payouts&lt;/a&gt;, and &lt;a href="https://docs.rapyd.net/en/subscriptions.html" rel="noopener noreferrer"&gt;manage subscription billing&lt;/a&gt;, all within a single platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for global payouts in multiple currencies:&lt;/strong&gt; Rapyd supports payouts to &lt;a href="https://docs.rapyd.net/en/global.html" rel="noopener noreferrer"&gt;over 190 countries in more than 70 currencies&lt;/a&gt;. This helps creators send and receive payments globally without dealing with separate banking integrations or third-party processors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced security with tokenization and PCI compliance:&lt;/strong&gt; Rapyd provides security features like &lt;a href="https://docs.rapyd.net/en/external-network-tokenization.html" rel="noopener noreferrer"&gt;end-to-end tokenization&lt;/a&gt;, and sensitive data like card details is never stored on your servers. It also offers built-in &lt;a href="https://www.pcisecuritystandards.org/" rel="noopener noreferrer"&gt;PCI DSS&lt;/a&gt; compliance, helping platforms meet regulatory requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing a Creator Payment System with the Rapyd API
&lt;/h2&gt;

&lt;p&gt;In this walkthrough, you'll learn how to build a basic creator payment system using Rapyd's API—covering royalties, affiliate payouts, and subscription management. We'll use Python and &lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt; to bring it all together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you begin, you'll need the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://dashboard.rapyd.net/sign-up" rel="noopener noreferrer"&gt;free Rapyd sandbox account&lt;/a&gt;. After registering, go to the &lt;strong&gt;Developers&lt;/strong&gt; section to find your access key and secret key—you'll need these to access Rapyd's APIs.
&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.amazonaws.com%2Fuploads%2Farticles%2Ffyyw5azjbwg14bpkwfz0.png" alt="Rapyd Developers page" width="800" height="401"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/downloads/release/python-390/" rel="noopener noreferrer"&gt;Python 3.8+&lt;/a&gt; installed on your system.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt;, &lt;a href="https://www.uvicorn.org/" rel="noopener noreferrer"&gt;uvicorn&lt;/a&gt;, &lt;a href="https://pypi.org/project/requests/" rel="noopener noreferrer"&gt;Requests&lt;/a&gt;, and &lt;a href="https://pypi.org/project/python-dotenv/" rel="noopener noreferrer"&gt;python-dotenv&lt;/a&gt; libraries. You can install these with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install fastapi uvicorn requests python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need to create a folder to store all your scripts and files. You can name it something like &lt;code&gt;creator_economy_rapyd_implementation&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Rapyd Authentication File
&lt;/h3&gt;

&lt;p&gt;Rapyd requires authentication to use its services. It's recommended that you use a &lt;code&gt;.env&lt;/code&gt; file to store all your credentials.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file inside your project folder and write the following lines of code, replacing &lt;code&gt;your_access_key_here&lt;/code&gt; and &lt;code&gt;your_secret_key_here&lt;/code&gt; with your actual credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RAPYD_ACCESS_KEY=your_access_key_here
RAPYD_SECRET_KEY=your_secret_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create another file named &lt;code&gt;rapyd_utils.py&lt;/code&gt; inside the same folder and import the necessary Python libraries:&lt;br&gt;
&lt;/p&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the libraries are imported, load the environment variables mentioned in the &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, load the Rapyd credentials and define the Rapyd URL you'll use to access their services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;access_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAPYD_ACCESS_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;secret_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RAPYD_SECRET_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;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://sandboxapi.rapyd.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've loaded your Rapyd credentials, it's time to implement Rapyd authentication with the following lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;separators&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;,&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;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
    &lt;span class="n"&gt;to_sign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;http_method&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;url_path&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;access_key&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;body_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;to_sign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&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;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function generates a signature to validate each request made to Rapyd's API. To ensure the request is coming from a validated source, Rapyd needs each request to be signed using &lt;a href="https://docs.rapyd.net/en/request-signatures.html" rel="noopener noreferrer"&gt;HMAC-SHA256&lt;/a&gt;. This code uses the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;url_path&lt;/code&gt;: The endpoint being called, like &lt;code&gt;/v1/account/transfer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;salt&lt;/code&gt;: A randomly generated string to prevent replay attacks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;timestamp&lt;/code&gt;: The current UNIX timestamp to ensure freshness.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;body&lt;/code&gt;: An optional request body for POST and PUT methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, define a function named &lt;code&gt;make_rapyd_request&lt;/code&gt; that will help you call various Rapyd services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_rapyd_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&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;access_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;access_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;salt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;signature&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="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;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&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="n"&gt;headers&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="n"&gt;body&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code can be broken down into the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It first creates a 12-byte random value (&lt;code&gt;salt&lt;/code&gt;) to ensure uniqueness.&lt;/li&gt;
&lt;li&gt;It then generates a &lt;code&gt;timestamp&lt;/code&gt; to prevent replay attacks.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generate_signature()&lt;/code&gt; combines the HTTP method, path, salt, timestamp, and body to create a secure signature for authentication.&lt;/li&gt;
&lt;li&gt;The code sets up the request headers, including authentication and content type.&lt;/li&gt;
&lt;li&gt;It executes the HTTP request using the HTTP method and endpoint.&lt;/li&gt;
&lt;li&gt;Finally, the code parses the server's response into JSON.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's all you need to do to make a request to Rapyd endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create an Rapyd API App
&lt;/h3&gt;

&lt;p&gt;Once the Rapyd utility script is ready, it's time to implement the main functionalities of a creator economy payment system.&lt;/p&gt;

&lt;p&gt;To start, you need to create &lt;code&gt;main.py&lt;/code&gt; inside your project folder. Your folder structure should look 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;creator_economy_rapyd_implementation/
│
├── main.py
├── rapyd_utils.py
└── .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the necessary Python libraries as well as the FastAPI library for creating different payment facility endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rapyd_utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;make_rapyd_request&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, write the different data models that define and validate the expected JSON input for the endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ---------- MODELS ----------
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Collaborator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;wallet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoyaltyRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creator_wallet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;total_revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;collaborators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Collaborator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AffiliateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;affiliate_wallet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;commission_amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;sender_wallet_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscriptionRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;customer_email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monthly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four different data models are defined: &lt;code&gt;Collaborator&lt;/code&gt; defines an individual involved in royalty revenue sharing, and the remaining three data models define and validate the payment facilities in the creator economy app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Royalty Payments
&lt;/h4&gt;

&lt;p&gt;Royalty payments are often recurring and variable, and they need to be distributed across multiple parties. To handle this efficiently, you need to build a system that supports accurate and timely royalty disbursements.&lt;/p&gt;

&lt;p&gt;In this section, you'll learn how to implement the first endpoint of the application for royalty payments. This is the foundation for handling complex, multiparty creator compensation.&lt;/p&gt;

&lt;p&gt;Add the following endpoint to your FastAPI application inside the &lt;code&gt;main.py&lt;/code&gt; file. This endpoint takes a list of collaborators and their revenue share, calculates each payout, and initiates the transfer to each collaborator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.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;/royalty-payments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;distribute_royalties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RoyaltyRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;collab&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collaborators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payout_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_revenue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;share&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;body&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;beneficiary&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;ewallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wallet_id&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;payout_amount&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;USD&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;sender&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;ewallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creator_wallet_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&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;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;royalty&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_rapyd_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&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;/v1/account/transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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;collab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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;payout_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;FAILED&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payouts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code uses the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data.collaborators&lt;/code&gt; calculates how much each collaborator should receive.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;round(..., 2)&lt;/code&gt; rounds off the obtained value up to two decimal points.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;beneficiary&lt;/code&gt; represents who gets the money.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sender&lt;/code&gt; is the creator paying the beneficiary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata&lt;/code&gt; is an optional tag to label this as a royalty payout.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make_rapyd_request&lt;/code&gt; calls the Rapyd API to perform the transfer and returns a list of each collaborator, their payout, and the API status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This endpoint is designed to split the revenue among multiple collaborators based on predefined share percentages. Once the request is received, including the creator's wallet ID, total revenue amount, and a list of collaborators, it calculates the payout amount by multiplying the total revenue by their share. The revenue is then rounded off to two decimal places for currency precision, and a JSON body is prepared that includes the sender and receiver wallet IDs, amount, currency, and metadata, indicating this is a "royalty" type of transfer. Finally, it makes a POST request to the Rapyd &lt;code&gt;/v1/account/transfer&lt;/code&gt; endpoint for each collaborator using the &lt;code&gt;make_rapyd_request&lt;/code&gt; function.&lt;/p&gt;

&lt;h4&gt;
  
  
  Affiliate Payouts
&lt;/h4&gt;

&lt;p&gt;Now, let's implement an endpoint that pays a single affiliate based on the provided data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.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;/affiliate-payout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_affiliate_payout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AffiliateRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&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;beneficiary&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;ewallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;affiliate_wallet_id&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commission_amount&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;USD&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;sender&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;ewallet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender_wallet_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&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;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;affiliate_commission&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_rapyd_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&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;/v1/account/transfer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&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;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;body{...}&lt;/code&gt; variable is almost identical to the one in the &lt;code&gt;/royalty-payments&lt;/code&gt; endpoint, but with different metadata. &lt;code&gt;make_rapyd_request()&lt;/code&gt; performs and returns the result of the payout.&lt;/p&gt;

&lt;p&gt;This endpoint accepts a JSON payload containing the affiliate's wallet ID, commission amount, and sender's wallet ID. It then constructs a body similar to the previous endpoint, defining the beneficiary, sender, currency, and amount, and tags the metadata with &lt;code&gt;affiliate_commission&lt;/code&gt;. Finally, it makes a POST request to Rapyd's &lt;code&gt;/v1/account/transfer&lt;/code&gt; endpoint to transfer the commission from the sender's wallet to the affiliate's.&lt;/p&gt;

&lt;h4&gt;
  
  
  Managing Subscriptions
&lt;/h4&gt;

&lt;p&gt;Finally, let's implement the last endpoint for managing subscriptions. This endpoint implements a hosted payment link via Rapyd's &lt;code&gt;/v1/payment_links&lt;/code&gt; API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.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;/create-subscription&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SubscriptionRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&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;USD&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;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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_email&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&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;subscription_tier&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;premium&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_rapyd_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&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;/v1/payment_links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redirect_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&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="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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redirect_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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 following variables are used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;interval&lt;/code&gt;, which defines recurring subscriptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata&lt;/code&gt;, which can be used to track tiers (basic, premium, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make_rapyd_request&lt;/code&gt;, which returns a redirect URL that the frontend or customer can use to pay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This endpoint accepts the customer's email, the subscription amount, and the payment interval. Then, it builds a request body that includes the amount, currency, customer details, subscription interval, and optional metadata like the tier name ("premium"). Finally, the body is sent to Rapyd's &lt;code&gt;/v1/payment_links&lt;/code&gt; endpoint, which generates a hosted link that customers can visit to complete the subscription setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the Application
&lt;/h3&gt;

&lt;p&gt;Once you've written the code, all you have to do is run the FastAPI app using &lt;code&gt;uvicorn&lt;/code&gt; to access different endpoints. To do so, open your terminal and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvicorn main:app --reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts your FastAPI application, which can be accessed on &lt;a href="http://localhost:8000/docs" rel="noopener noreferrer"&gt;http://localhost:8000/docs&lt;/a&gt;:&lt;/p&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.amazonaws.com%2Fuploads%2Farticles%2F05atyu8uf7qg6awafz4w.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.amazonaws.com%2Fuploads%2Farticles%2F05atyu8uf7qg6awafz4w.png" alt="Creator payment app" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use curl commands to call the following endpoints: &lt;code&gt;/royalty-payments&lt;/code&gt;, &lt;code&gt;/affiliate-payout&lt;/code&gt;, and &lt;code&gt;/create-subscription&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  /royalty-payments
&lt;/h4&gt;

&lt;p&gt;This endpoint splits and distributes revenue among collaborators based on their share. To do this, you need to obtain a wallet ID (such as &lt;code&gt;ewallet_creator_123&lt;/code&gt;) from Rapyd. Every payment in the application—whether a royalty payout, affiliate commission, or subscription—depends on &lt;a href="https://docs.rapyd.net/en/rapyd-wallet.html" rel="noopener noreferrer"&gt;Rapyd Wallets&lt;/a&gt;, also known as ewallets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8000/royalty-payments \
-H "Content-Type: application/json" \
-d '{
  "creator_wallet_id": "ewallet_creator_123",
  "total_revenue": 1000,
  "collaborators": [
    {
      "name": "Alice",
      "wallet_id": "ewallet_alice_001",
      "share": 0.5
    },
    {
      "name": "Bob",
      "wallet_id": "ewallet_bob_002",
      "share": 0.3
    },
    {
      "name": "Charlie",
      "wallet_id": "ewallet_charlie_003",
      "share": 0.2
    }
  ]
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will look 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;{
  "payouts": [
    {
      "name": "Alice",
      "amount": 500.0,
      "status": "SUCCESS"
    },
    {
      "name": "Bob",
      "amount": 300.0,
      "status": "SUCCESS"
    },
    {
      "name": "Charlie",
      "amount": 200.0,
      "status": "SUCCESS"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint splits $1,000 USD in revenue into 50 percent, 30 percent, and 20 percent shares for Alice, Bob, and Charlie, respectively. Then, it initiates Rapyd transfers for each. The response confirms the transfer status for each payout.&lt;/p&gt;

&lt;h4&gt;
  
  
  /affiliate-payout
&lt;/h4&gt;

&lt;p&gt;This endpoint pays a commission to an affiliate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8000/affiliate-payout \
-H "Content-Type: application/json" \
-d '{
  "affiliate_wallet_id": "ewallet_aff_007",
  "commission_amount": 120,
  "sender_wallet_id": "ewallet_creator_123"
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will look 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;{
  "status": {
    "status": "SUCCESS",
    "message": "",
    "error_code": "",
    "response_code": "",
    "operation_id": "e4bc3f56-d40b-4cb4-a9e1-e5d18fc77b8e"
  },
  "data": {
    "id": "transfer_abc123xyz",
    "amount": 120,
    "currency": "USD",
    "status": "ACT",
    "beneficiary": {
      "ewallet": "ewallet_aff_007"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint transfers $120 USD from the creator's wallet to the affiliate's wallet. The &lt;code&gt;operation_id&lt;/code&gt; can be stored for logging or future reconciliation.&lt;/p&gt;

&lt;h4&gt;
  
  
  /create-subscription
&lt;/h4&gt;

&lt;p&gt;This endpoint creates a payment link for a customer to subscribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:8000/create-subscription \
-H "Content-Type: application/json" \
-d '{
  "customer_email": "subscriber@example.com",
  "amount": 15.99,
  "interval": "monthly"
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output will look 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;{
  "redirect_url": "https://sandboxcheckout.rapyd.net?token=pl_8fc456a83e"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a hosted payment link. Once you click the URL, you'll be taken to a Rapyd-hosted checkout page where you can securely complete the subscription process using your preferred payment method.&lt;/p&gt;

&lt;p&gt;The API call allows customers to subscribe to a monthly plan priced at $15.99 USD. You can also use &lt;a href="https://docs.rapyd.net/en/subscription-webhooks.html" rel="noopener noreferrer"&gt;webhooks in Rapyd&lt;/a&gt; to monitor subscription lifecycle events like &lt;code&gt;subscription.created&lt;/code&gt;, &lt;code&gt;payment.completed&lt;/code&gt;, or &lt;code&gt;subscription.cancelled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, you've created an application that can manage royalties, payouts, and subscriptions with Rapyd. Congratulations! You can find the full code for this tutorial on &lt;a href="https://github.com/Rapyd-Samples/Creator_Economy_How_To_Manage_Royalties_Payouts_and_Subscriptions_with_Rapyd" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The financial side of content creation can be hard to manage, but the right tools can make it easier. &lt;a href="https://www.rapyd.net/" rel="noopener noreferrer"&gt;Rapyd&lt;/a&gt; helps simplify payment operations so creators can spend less time on logistics and more time on creating.&lt;/p&gt;

&lt;p&gt;Rapyd is more than just a payment provider; it's an all-in-one fintech infrastructure that helps you move money with ease. Whether you're managing complex royalty splits, scaling affiliate payouts, or automating recurring subscriptions, Rapyd's APIs will help you build powerful, secure, and flexible payment systems quickly.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://docs.rapyd.net/en/developers.html" rel="noopener noreferrer"&gt;Client Portal&lt;/a&gt; or play around with &lt;a href="https://docs.rapyd.net/en/environments.html" rel="noopener noreferrer"&gt;Rapyd's Sandbox&lt;/a&gt;—you'll quickly see how easy it is to streamline payments and simplify your workflow with Rapyd.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mastering Compliance API Integrations: A Developer's Guide to AI-Powered KYC, AML, &amp; Fraud Prevention</title>
      <dc:creator>Drew Harris</dc:creator>
      <pubDate>Thu, 03 Jul 2025 12:42:31 +0000</pubDate>
      <link>https://dev.to/rapyd/mastering-compliance-api-integrations-a-developers-guide-to-ai-powered-kyc-aml-fraud-4e06</link>
      <guid>https://dev.to/rapyd/mastering-compliance-api-integrations-a-developers-guide-to-ai-powered-kyc-aml-fraud-4e06</guid>
      <description>&lt;p&gt;The rapid evolution of AI is fundamentally reshaping regulatory compliance in payments. For us developers on the front lines, this transformation often manifests through the integration of sophisticated &lt;strong&gt;AI-powered APIs&lt;/strong&gt; for critical functions like &lt;strong&gt;Know Your Customer (KYC)&lt;/strong&gt;, &lt;strong&gt;Anti-Money Laundering (AML)&lt;/strong&gt;, and real-time &lt;strong&gt;fraud prevention&lt;/strong&gt;. This isn't just about automation; it's about embedding intelligent decision-making directly into your payment flows, making your systems smarter and more resilient.&lt;/p&gt;

&lt;p&gt;This article, part of our series on AI in payments compliance (and building on our strategic overview), serves as your direct playbook for navigating the world of compliance API integrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Golden Nugget: APIs as Your Compliance Backbone
&lt;/h2&gt;

&lt;p&gt;Here's the core insight for us as developers: you don't need to become an AI expert or a compliance lawyer. Instead, your superpower lies in effectively integrating and orchestrating best-in-class, specialized compliance APIs. These services, often powered by advanced machine learning, handle the heavy lifting of data analysis, risk scoring, and pattern recognition, providing you with clear, actionable outputs you can consume directly in your code. This is where the magic happens – leveraging intelligence without building it from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. API Selection Criteria: Choosing Your Compliance Partner Wisely
&lt;/h2&gt;

&lt;p&gt;Before writing a single line of code, let's talk strategy. What makes a compliance API robust and reliable for a production payment system?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global Coverage &amp;amp; Data Sources:&lt;/strong&gt; Does it cover the jurisdictions where your users operate? Leading providers like &lt;strong&gt;ComplyCube, Uniify, Jumio, and Onfido&lt;/strong&gt; offer extensive global identity verification and screening. Don't get caught out by regional limitations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Processing &amp;amp; Latency:&lt;/strong&gt; For payment transactions, every millisecond counts. Prioritize APIs designed for near-instant responses to minimize friction in user journeys and prevent real-time fraud. Think about your SLAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Enrichment Capabilities:&lt;/strong&gt; Beyond simple validation, can the API enrich data? For example, can it provide a risk score based on an IP address, email, or device fingerprint, as offered by solutions like &lt;strong&gt;SEON&lt;/strong&gt; or &lt;strong&gt;IPQualityScore&lt;/strong&gt;? This extra context is invaluable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation Quality (Critically Important!):&lt;/strong&gt; This is paramount. Comprehensive, clear, and interactive API documentation (e.g., Swagger UI/OpenAPI spec) with robust code examples in multiple languages drastically reduces integration time and errors. If the docs are poor, run!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox Environments &amp;amp; Support:&lt;/strong&gt; A robust, feature-rich sandbox for development and testing, coupled with responsive developer support channels, are non-negotiable. Test, test, test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing Models:&lt;/strong&gt; Understand if it's per-API call, tiered, or based on usage volume. Model costs against your projected transaction volumes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Integration Patterns: Weaving Compliance into Your Workflow
&lt;/h2&gt;

&lt;p&gt;Compliance checks need to happen at various, often distinct, points in the payment lifecycle. Understanding the right integration pattern for each is key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h3&gt;
  
  
  Onboarding (KYC/KYB): The Asynchronous Dance
&lt;/h3&gt;

&lt;p&gt;Integrate identity and business verification APIs during user or merchant signup. This is often an asynchronous process, where initial checks trigger background processes, allowing the user to continue while comprehensive checks complete.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pattern: Request-Response with Asynchronous Callbacks (Webhooks).&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode for KYC Onboarding Flow
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initiate_kyc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_unique_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Send request to compliance API with your webhook endpoint
&lt;/span&gt;    &lt;span class="n"&gt;compliance_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_kyc_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_data&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;callback_url&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://your-domain.com/webhooks/kyc-status](https://your-domain.com/webhooks/kyc-status)&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;metadata&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;transactionId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;save_pending_kyc_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&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;PENDING_REVIEW&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;transactionId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Your webhook endpoint: Called by compliance API when status changes
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/webhooks/kyc-status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&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;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_kyc_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;transaction_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;transactionId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;details&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# IMPORTANT: Validate webhook signature here!
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;validate_webhook_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-Signature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;YOUR_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid signature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;

    &lt;span class="nf"&gt;update_kyc_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APPROVED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;activate_user_account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="c1"&gt;# Acknowledge receipt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An initial API call validates basic info, and a webhook might notify your system when comprehensive background checks (e.g., identity verification, sanctions screening) are complete.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;h3&gt;
  
  
  Transaction Processing (AML/Fraud): The Real-Time Gatekeeper
&lt;/h3&gt;

&lt;p&gt;Implement real-time calls to fraud prevention and AML APIs &lt;em&gt;before&lt;/em&gt; a transaction is authorized. These systems analyze transactional data (amount, location, payment method, card BIN) and behavioral signals to generate a risk score.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pattern: Synchronous Request-Response.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Pseudocode for Real-time Transaction Fraud Check
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_details&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="n"&gt;fraud_check_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;fraud_api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fraud_check_result&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;riskScore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD_REJECT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&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;REJECTED&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;reason&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;High fraud risk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fraud_check_result&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;riskScore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;THRESHOLD_REVIEW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Log for manual review, but allow transaction to proceed conditionally
&lt;/span&gt;            &lt;span class="nf"&gt;log_for_manual_review&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_details&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fraud_check_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Proceed with payment authorization
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payment_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Implement graceful fallback or block transaction
&lt;/span&gt;        &lt;span class="nf"&gt;handle_fraud_api_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&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;ERROR&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;message&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;Fraud check failed&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;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API call should be fast enough not to introduce significant latency into the checkout flow (aim for single-digit milliseconds). &lt;strong&gt;Google's reCAPTCHA Fraud Prevention&lt;/strong&gt; and &lt;strong&gt;SEON&lt;/strong&gt; are excellent examples of solutions for real-time risk assessments.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;h3&gt;
  
  
  Post-Transaction Monitoring: The Background Watcher
&lt;/h3&gt;

&lt;p&gt;For ongoing AML and suspicious activity detection, integrate APIs that monitor transaction streams or user behavior over time. This is less about blocking and more about continuous vigilance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pattern: Event-Driven Architecture with Webhooks.&lt;/strong&gt; Your system sends transaction data (or batches of data) to a monitoring service, and that service uses webhooks to notify your application of any suspicious activity or alerts that require review. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Data Flow &amp;amp; Security: Protecting Sensitive Information is Non-Negotiable
&lt;/h2&gt;

&lt;p&gt;You're dealing with highly sensitive financial and personal data. &lt;strong&gt;Security is not an afterthought; it's fundamental&lt;/strong&gt; to every line of code you write.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure Transmission:&lt;/strong&gt; Always, always, &lt;em&gt;always&lt;/em&gt; use &lt;strong&gt;HTTPS/TLS&lt;/strong&gt; for all API communications. No excuses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication &amp;amp; Authorization:&lt;/strong&gt; Implement robust API key management, OAuth 2.0, or OpenID Connect. Store API keys securely (e.g., in environment variables, secret management services like AWS Secrets Manager or HashiCorp Vault), never hardcode them. Rotate keys regularly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Minimization:&lt;/strong&gt; Only send the &lt;em&gt;necessary&lt;/em&gt; data to the API. Avoid transmitting sensitive information that isn't required for the specific compliance check. The less data you send, the less surface area for compromise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption &amp;amp; Tokenization:&lt;/strong&gt; Where possible, &lt;strong&gt;tokenize or encrypt&lt;/strong&gt; sensitive data (e.g., payment card numbers, personally identifiable information (PII)) before sending it to third-party APIs. This limits exposure even if a breach occurs. Think of the Payment Card Industry Data Security Standard (PCI DSS) implications here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input/Output Schemas:&lt;/strong&gt; Understand the exact data formats (JSON, XML) and required fields for API requests and responses. Implement strong input &lt;strong&gt;validation&lt;/strong&gt; on &lt;em&gt;all&lt;/em&gt; data sent to and received from APIs to prevent injection attacks or unexpected behavior.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Error Handling &amp;amp; Fallbacks: Building Resilient Systems
&lt;/h2&gt;

&lt;p&gt;External APIs can fail, encounter rate limits, or return unexpected errors. Your integration must be resilient, just like any mission-critical system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Status Codes:&lt;/strong&gt; Know your common API error codes (e.g., &lt;code&gt;400 Bad Request&lt;/code&gt;, &lt;code&gt;401 Unauthorized&lt;/code&gt;, &lt;code&gt;403 Forbidden&lt;/code&gt;, &lt;code&gt;429 Too Many Requests&lt;/code&gt;, &lt;code&gt;500 Internal Server Error&lt;/code&gt;, &lt;code&gt;503 Service Unavailable&lt;/code&gt;). Each has a specific meaning and demands a specific response from your code.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rate Limit Management:&lt;/strong&gt; Implement client-side rate limiting or use strategies like &lt;strong&gt;exponential backoff with jitter&lt;/strong&gt; for retries when encountering &lt;code&gt;429 Too Many Requests&lt;/code&gt;. The &lt;code&gt;Retry-After&lt;/code&gt; HTTP header is your friend here, indicating when you can safely retry.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="c1"&gt;# Assuming 'requests' library for HTTP calls
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_api_call_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_client_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;api_client_method&lt;/span&gt;&lt;span class="p"&gt;()&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;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Raises HTTPError for bad responses (4xx or 5xx)
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Too Many Requests
&lt;/span&gt;                &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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="c1"&gt;# Exponential backoff + jitter
&lt;/span&gt;                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limit hit. Retrying in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="c1"&gt;# Re-raise other HTTP errors
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Handle network issues more gracefully
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection error. Retrying in 1 second...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&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;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Max retries exceeded for API call.&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;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Circuit Breakers &amp;amp; Timeouts:&lt;/strong&gt; Implement &lt;strong&gt;circuit breakers&lt;/strong&gt; to prevent cascading failures if an API becomes unresponsive. Set appropriate, aggressive timeouts for API calls to avoid hanging requests. Libraries like Hystrix (Java) or Polly (.NET) provide patterns for this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fallback Mechanisms:&lt;/strong&gt; For non-critical checks, consider &lt;strong&gt;graceful degradation&lt;/strong&gt;. What happens if a fraud API is down? Can you temporarily switch to a more conservative internal rule set, or queue checks for later processing when the service recovers? Always have a plan B.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Webhooks &amp;amp; Event-Driven Architecture: Powering Asynchronous Processing
&lt;/h2&gt;

&lt;p&gt;For ongoing monitoring and long-running processes (like comprehensive AML screening or adverse media checks), webhooks are indispensable for an &lt;strong&gt;event-driven architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Listen for Events:&lt;/strong&gt; Your application exposes a secure endpoint that the compliance API can call when an event occurs (e.g., a screening result is ready, a fraud alert is triggered).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validate Webhook Signatures:&lt;/strong&gt; &lt;strong&gt;Always, always verify the signature of incoming webhooks.&lt;/strong&gt; This ensures the webhook genuinely originates from the compliance provider and hasn't been tampered with by a malicious actor.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_webhook_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Example for HMAC-SHA256 signature validation
&lt;/span&gt;    &lt;span class="c1"&gt;# The specific header name and signature generation method vary by API provider.
&lt;/span&gt;    &lt;span class="n"&gt;expected_signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;request_body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Use a constant-time comparison to prevent timing attacks
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected_signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature_header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; Design your webhook handlers to be &lt;strong&gt;idempotent&lt;/strong&gt;. This means processing the same event multiple times won't cause adverse effects, as webhooks can sometimes be delivered more than once due to network issues or retries. Store a unique event ID and check if it's already processed before taking action.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Testing Methodologies: Ensuring Compliance and Performance Under Pressure
&lt;/h2&gt;

&lt;p&gt;Thorough testing is paramount for compliance APIs, given the financial and regulatory stakes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox Environments:&lt;/strong&gt; Conduct initial development and integration testing exclusively in the API provider's sandbox. This is your safe playpen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit &amp;amp; Integration Tests:&lt;/strong&gt; Write comprehensive unit tests for your API integration logic, verifying request formatting, response parsing, and error handling. Then, integration tests against the sandbox to confirm end-to-end flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance &amp;amp; Load Testing:&lt;/strong&gt; Simulate realistic high transaction volumes. Can your integration handle the anticipated load without bottlenecks? Does the compliance API meet your latency requirements under stress?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge Case Testing:&lt;/strong&gt; This is where you find the tricky bugs. Test scenarios like malformed data, very high/low transaction amounts, unusual geo-locations, and identity mismatches to ensure the API responds as expected and your code handles them gracefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Audits:&lt;/strong&gt; Regularly conduct security audits and penetration tests on your integration points. This includes checking for API key exposure, data leakage, and potential for abuse.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Integrating AI-powered compliance APIs is rapidly becoming a fundamental skill set for modern payment developers. By meticulously selecting the right APIs, designing robust integration patterns, prioritizing data security, implementing resilient error handling, leveraging event-driven architectures, and rigorously testing your solutions, you can significantly enhance your payment gateway's security posture and regulatory adherence.&lt;/p&gt;

&lt;p&gt;This practical, API-first approach to AI implementation empowers you to build highly intelligent, adaptive, and compliant payment systems. It allows you to future-proof your solutions, moving beyond basic automation to truly intelligent, adaptive compliance that supports innovation rather than hindering it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What compliance APIs have &lt;em&gt;you&lt;/em&gt; found most effective in your projects? What integration challenges have you overcome? Share your experiences and insights in the comments below!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is part of a series stemming from our comprehensive analysis: &lt;a href="https://community.rapyd.net/t/ai-for-regulatory-compliance-in-payments/59638" rel="noopener noreferrer"&gt;https://community.rapyd.net/t/ai-for-regulatory-compliance-in-payments/59638&lt;/a&gt; Explore the other articles in the series to dive deeper into Explainable AI (XAI) and MLOps for compliance!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>payments</category>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
