<?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: Amit Tanwar</title>
    <description>The latest articles on DEV Community by Amit Tanwar (@amit_tanwar_44895e1d86fbf).</description>
    <link>https://dev.to/amit_tanwar_44895e1d86fbf</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%2Fuser%2Fprofile_image%2F3918206%2F6a8267da-f6e6-475c-a92d-f80f9f9ee5dd.png</url>
      <title>DEV Community: Amit Tanwar</title>
      <link>https://dev.to/amit_tanwar_44895e1d86fbf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amit_tanwar_44895e1d86fbf"/>
    <language>en</language>
    <item>
      <title>Every Integration Wants Something Different</title>
      <dc:creator>Amit Tanwar</dc:creator>
      <pubDate>Thu, 14 May 2026 07:49:40 +0000</pubDate>
      <link>https://dev.to/amit_tanwar_44895e1d86fbf/every-integration-wants-something-different-5fce</link>
      <guid>https://dev.to/amit_tanwar_44895e1d86fbf/every-integration-wants-something-different-5fce</guid>
      <description>&lt;p&gt;&lt;em&gt;How to keep your core clean when the outside world refuses to be consistent.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A Problem Every Backend Engineer Hits
&lt;/h2&gt;

&lt;p&gt;At some point, you build a system that needs to talk to multiple external services. Maybe it's payment providers, cloud storage buckets, notification services, or data warehouses. The business logic is the same across all of them. But every single one has a different idea of what "connecting" looks like.&lt;/p&gt;

&lt;p&gt;One wants an API key. Another wants a key and a secret. A third wants a username and password. A fourth wants a signed certificate.&lt;/p&gt;

&lt;p&gt;None of them are wrong. They just aren't consistent with each other.&lt;/p&gt;

&lt;p&gt;So the question becomes: where does all that difference live in your system?&lt;/p&gt;




&lt;h2&gt;
  
  
  Where It Usually Goes Wrong
&lt;/h2&gt;

&lt;p&gt;The path of least resistance is to pass the raw config straight through and let the core figure it out. It works fine for the first integration. By the third it starts getting messy. By the tenth, your core is full of conditionals 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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&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;else&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;adyen&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;merchantAccount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;merchantAccount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;braintree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&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;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateKey&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Basic &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="c1"&gt;// ... keeps growing with every new integration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the core knows the internal shape of every integration's credentials. Adding a new integration means touching the core. Rotating a credential means hunting through the core to find every reference. Testing the core means setting up mocks for every integration's config shape.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The core didn't get complex because the business logic got complex. It got complex because integration details had no other place to go.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Pattern: Absorb at the Boundary
&lt;/h2&gt;

&lt;p&gt;The fix is simple in concept. Integration-specific config belongs at the boundary, not in the core.&lt;/p&gt;

&lt;p&gt;The boundary is the place where your system first receives external input. It is the only place that should know about integration-specific shapes. Its job is to take all that variation, absorb it, and hand the core one consistent type regardless of which integration is being used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Integration A     Integration B     Integration C        │
│   { api_key }       { key + secret }  { user + pass }      │
│                                                             │
│        (each caller speaks a completely different language) │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                      The Boundary                           │
│                                                             │
│         absorbs all variation, translates everything        │
│               into one single canonical form                │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                   Canonical Config                          │
│                                                             │
│       one consistent shape, the only thing                  │
│               the core ever sees                            │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what that changes in practice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Without a boundary&lt;/th&gt;
&lt;th&gt;With a boundary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core receives&lt;/td&gt;
&lt;td&gt;Raw integration config&lt;/td&gt;
&lt;td&gt;One canonical type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding an integration&lt;/td&gt;
&lt;td&gt;Touches core logic&lt;/td&gt;
&lt;td&gt;Adds a boundary variant only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credential rotation&lt;/td&gt;
&lt;td&gt;Trace through the entire core&lt;/td&gt;
&lt;td&gt;Update the boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core tests&lt;/td&gt;
&lt;td&gt;Need every integration's config&lt;/td&gt;
&lt;td&gt;Independent of integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Hard Part: When Config Changes Per Request
&lt;/h2&gt;

&lt;p&gt;Static config is the easy case. The harder case is when a caller wants to override something at request time. Maybe they want to point an integration at a staging environment, or use a different endpoint for one specific request.&lt;/p&gt;

&lt;p&gt;The tempting fix is to pass the override straight into the core and handle it there. But the moment you do that, the boundary breaks. The core starts needing to know that integration A has a &lt;code&gt;disputeUrl&lt;/code&gt; field and integration B has a &lt;code&gt;secondaryEndpoint&lt;/code&gt; field. You are back to the same problem.&lt;/p&gt;

&lt;p&gt;The right approach is to keep overrides behind the boundary too. The boundary accepts the override in the integration's own language, extracts what is relevant, merges it into the canonical form, and only then passes it to the core.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────┐
│  Request arrives with an integration-specific override   │
│  e.g. "use this staging URL for this request"            │
└─────────────────────────┬────────────────────────────────┘
                          │
                          ▼
┌──────────────────────────────────────────────────────────┐
│                    The Boundary                          │
│                                                          │
│  1. Accept override in the integration's own language    │
│  2. Extract only what is relevant                        │
│  3. Merge into the canonical form                        │
└─────────────────────────┬────────────────────────────────┘
                          │
                          ▼
┌──────────────────────────────────────────────────────────┐
│         Core receives the merged canonical config        │
│                                                          │
│    No knowledge of the override.                         │
│    No knowledge of which integration was called.         │
└──────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core gets a clean, merged config. It never knows an override happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Prism Does This Across 60+ Payment Connectors
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hyperswitch.io" rel="noopener noreferrer"&gt;Prism&lt;/a&gt; is a payment infrastructure layer that routes transactions across 60+ payment connectors including Stripe, Adyen, Braintree, Razorpay, and many more. Every connector has a different credential shape. This is exactly the problem from above, running in production at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  The vocabulary problem
&lt;/h3&gt;

&lt;p&gt;Here is a sample of what a few real connectors expect:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Connector&lt;/th&gt;
&lt;th&gt;Credential fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api_key&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adyen&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;api_key&lt;/code&gt; + &lt;code&gt;merchant_account&lt;/code&gt; + optional &lt;code&gt;review_key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorize.net&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;name&lt;/code&gt; + &lt;code&gt;transaction_key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluesnap&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;username&lt;/code&gt; + &lt;code&gt;password&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Braintree&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;public_key&lt;/code&gt; + &lt;code&gt;private_key&lt;/code&gt; + optional &lt;code&gt;merchant_account_id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revolut&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;secret_api_key&lt;/code&gt; + optional &lt;code&gt;signing_secret&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same concept across all of them: authenticate with this connector. Six completely different shapes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The boundary in Prism
&lt;/h3&gt;

&lt;p&gt;Prism defines a typed enum with one variant per connector. The caller sends credentials in their connector's own language. Prism parses it into the enum at the boundary. From that point on, the rest of the system never sees connector-specific field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One variant per connector, each capturing exactly what that connector needs&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ConnectorConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Stripe&lt;/span&gt;          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;api_key&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="n"&gt;Adyen&lt;/span&gt;           &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;api_key&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="n"&gt;merchant_account&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="n"&gt;review_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Authorizedotnet&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="n"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction_key&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="n"&gt;Bluesnap&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;username&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="n"&gt;password&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="n"&gt;Braintree&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;public_key&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="n"&gt;private_key&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="n"&gt;merchant_account_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Revolut&lt;/span&gt;         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;secret_api_key&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="n"&gt;signing_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// ... 60+ more&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the core sees
&lt;/h3&gt;

&lt;p&gt;The core works with a completely different type. A flat struct with just URLs. No credentials, no connector-specific field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// What the core always receives, regardless of which connector is being used&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ConnectorEndpoints&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dispute_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;secondary_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;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;Every connector collapses to this same shape before the core touches it. The core reads &lt;code&gt;endpoints.base_url&lt;/code&gt;. It has no idea which connector is on the other end or what credentials were used to get there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overrides in Prism
&lt;/h3&gt;

&lt;p&gt;When a caller wants to override a URL at request time, they include it in their connector config. Prism pulls it out at the boundary, translates it to the canonical field name, and merges it into &lt;code&gt;ConnectorEndpoints&lt;/code&gt; before anything reaches the core.&lt;/p&gt;

&lt;p&gt;The comment in the source code says it plainly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is the only path by which URL overrides in ConnectorConfig should influence request execution."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is no side door. Every connector-specific value, whether it is a credential or a URL override, goes through the boundary and gets translated before the core sees it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Get From This
&lt;/h2&gt;

&lt;p&gt;The payoff shows up in everyday engineering work.&lt;/p&gt;

&lt;p&gt;Adding a new connector means adding a variant to the boundary enum and writing a translator. The core stays untouched.&lt;/p&gt;

&lt;p&gt;Rotating a credential means updating the boundary. You do not need to trace where it flows through the rest of the system.&lt;/p&gt;

&lt;p&gt;Testing the core means testing against the canonical type. No integration-specific mock setup needed.&lt;/p&gt;

&lt;p&gt;Debugging is cleaner too. Integration-specific problem? Look at the boundary. Flow problem? Look at the core. The separation tells you exactly where to look.&lt;/p&gt;

&lt;p&gt;In Prism's case, 60+ connectors and the core has never needed a conditional for any of them. The boundary does the work so the core does not have to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The outside world will never be consistent. Every integration you connect will have a different shape, a different auth scheme, a different set of fields it cares about.&lt;/p&gt;

&lt;p&gt;That is fine, as long as your system has one place where all that inconsistency gets absorbed and translated. The core should never know what is on the other side of that boundary.&lt;/p&gt;

&lt;p&gt;The pattern itself is not complicated. The discipline to maintain it, to not let one quick fix bypass the boundary, is the hard part. But once it holds, adding integration number 61 costs exactly as much as adding integration number 1.&lt;/p&gt;

&lt;p&gt;That is the foundation &lt;a href="https://hyperswitch.io" rel="noopener noreferrer"&gt;Prism&lt;/a&gt; is built on. And it applies to any system that talks to more than one external service.&lt;/p&gt;




</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
