62 KiB
Guide to Integrating a Connector
Table of Contents
- Introduction
- Prerequisites
- Development Environment Setup & Configuration
- Create a Connector
- Test the Connection
- Folder Structure After Running the Script
- Common Payment Flow Types
- Integrate a New Connector
- Code Walkthrough
- Error Handling in Hyperswitch Connectors
- Implementing the Connector Interface
- ConnectorCommon: The Foundation Trait
- ConnectorIntegration – The Payment Flow Orchestrator
- Method-by-Method Breakdown
- Connector Traits Overview
- Derive Traits
- Connector Utility Functions
- Connector Configuration for Control Center Integration
- Control Center Frontend Integration
- Test the Connector Integration
Introduction
This guide provides instructions on integrating a new connector with Router, from setting up the environment to implementing API interactions. In this document you’ll learn how to:
- Scaffold a new connector template
- Define Rust request/response types directly from your PSP’s JSON schema
- Implement transformers and the
ConnectorIntegrationtrait for both standard auth and tokenization-first flows - Enforce PII best practices (Secret wrappers, common_utils::pii types) and robust error-handling
- Update the Control-Center (ConnectorTypes.res, ConnectorUtils.res, icons)
- Validate your connector with end-to-end tests
By the end, you’ll learn how to create a fully functional, production-ready connector—from blank slate to live in the Control-Center.
Prerequisites
- Before you begin, ensure you’ve completed the initial setup in our Hyperswitch Contributor Guide, which covers cloning, tool installation, and access.
- You should also understanding connectors and payment methods.
- Familiarity with the Connector API you’re integrating
- A locally set up and running Router repository
- API credentials for testing (sign up for sandbox/UAT credentials on the connector’s website).
- Need help? Join the Hyperswitch Slack Channel. We also have weekly office hours every Thursday at 8:00 AM PT (11:00 AM ET, 4:00 PM BST, 5:00 PM CEST, and 8:30 PM IST). Link to office hours are shared in the #general channel.
Development Environment Setup & Configuration
This guide will walk you through your environment setup and configuration.
Clone the Hyperswitch monorepo
git clone git@github.com:juspay/hyperswitch.git
cd hyperswitch
Rust Environment & Dependencies Setup
Before running Hyperswitch locally, make sure your Rust environment and system dependencies are properly configured.
Follow the guide:
Configure Rust and install required dependencies based on your OS
Quick links by OS:
All OS Systems:
-
Set up the Rust nightly toolchain installed for code formatting:
rustup toolchain install nightly
- Install Protobuf
Install cargo-generate for creating project templates:
cargo install cargo-generate
If you've completed the setup, you should now have:
- ✅ Rust & Cargo
- ✅
cargo-generate - ✅ PostgreSQL (with a user and database created)
- ✅ Redis
- ✅
diesel_cli - ✅ The
justcommand runner - ✅ Database migrations applied
- ✅ Set up the Rust nightly toolchain
- ✅ Installed Protobuf
Compile and run the application using cargo:
cargo run
Create a Connector
From the root of the project, generate a new connector by running the following command. Use a single-word name for your ConnectorName:
sh scripts/add_connector.sh <ConnectorName> <ConnectorBaseUrl>
When you run the script, you should see that some files were created
# Done! New project created /absolute/path/hyperswitch/crates/hyperswitch_connectors/src/connectors/connectorname
⚠️ Warning
Don’t be alarmed if you see test failures at this stage.
Tests haven’t been implemented for your new connector yet, so failures are expected.
You can safely ignore output like this:test result: FAILED. 0 passed; 20 failed; 0 ignored; 0 measured; 1759 filtered out; finished in 0.10sYou can also ignore GRPC errors too.
Test the connection
Once you've successfully created your connector using the add_connector.sh script, you can verify the integration by starting the Hyperswitch Router Service:
cargo r
This launches the router application locally on port 8080, providing access to the complete Hyperswitch API. You can now test your connector implementation by making HTTP requests to the payment endpoints for operations like:
- Payment authorization and capture
- Payment synchronization
- Refund processing
- Webhook handling
Once your connector logic is implemented, this environment lets you ensure it behaves correctly within the Hyperswitch orchestration flow—before moving to staging or production.
Verify Server Health
Once the Hyperswitch Router Service is running, you can verify it's operational by checking the health endpoint in a separate terminal window:
curl --head --request GET 'http://localhost:8080/health'
Action Item
After creating the connector, run a health check to ensure everything is working smoothly.
Folder Structure After Running the Script
When you run the script, it creates a specific folder structure for your new connector. Here's what gets generated:
Main Connector Files
The script creates the primary connector structure in the hyperswitch_connectors crate:
crates/hyperswitch_connectors/src/connectors/
├── <connector_name>/
│ └── transformers.rs
└── <connector_name>.rs
Test Files
The script also generates test files in the router crate:
crates/router/tests/connectors/
└── <connector_name>.rs
What Each File Contains
<connector_name>.rs: The main connector implementation file where you implement the connector traitstransformers.rs: Contains data structures and conversion logic between Hyperswitch's internal format and your payment processor's API format- Test file: Contains boilerplate test cases for your connector.
Common Payment Flow Types
As you build your connector, you’ll encounter different payment flow patterns.
This section gives you:
- A quick reference table for all flows
- Examples of the two most common patterns: Tokenization‑first and Direct Authorization
For full details, see Connector Payment Flow documentation or ask us in Slack.
1. Flow Summary Table
| Flow Name | Description | Implementation in Hyperswitch |
|---|---|---|
| Access Token | Obtain OAuth access token | crates/hyperswitch_interfaces/src/types.rs#L7 |
| Tokenization | Exchange credentials for a payment token | crates/hyperswitch_interfaces/src/types.rs#L148 |
| Customer Creation | Create or update customer records | crates/router/src/types.rs#L40 |
| Pre‑Processing | Validation or enrichment before auth | crates/router/src/types.rs#L41 |
| Authorization | Authorize and immediately capture payment | crates/hyperswitch_interfaces/src/types.rs#L12 |
| Authorization‑Only | Authorize payment for later capture | crates/router/src/types.rs#L39 |
| Capture | Capture a previously authorized payment | crates/router/src/types.rs#L39 |
| Refund | Issue a refund | crates/router/src/types.rs#L44 |
| Webhook Handling | Process asynchronous events from PSP | crates/router/src/types.rs#L45 |
Flow Type Definitions
Each flow type corresponds to specific request/response data structures and connector integration patterns. All flows follow a standardized pattern with associated:
- Request data types (e.g.,
PaymentsAuthorizeData) - Response data types (e.g.,
PaymentsResponseData) - Router data wrappers for connector communication
2. Pattern: Tokenization‑First
Some PSPs require payment data to be tokenized before it can be authorized.
This is a two‑step process:
-
Tokenization – e.g., Billwerk’s implementation:
-
Authorization – Uses the returned token rather than raw payment details.
Most PSPs don’t require this; see the next section for direct authorization.
3. Pattern: Direct Authorization
Many connectors skip tokenization and send payment data directly in the authorization request.
-
Authorize.net – code
BuildsCreateTransactionRequestdirectly from payment data inget_request_body(). -
Helcim – code
Chooses purchase (auto‑capture) or preauth endpoint inget_url()and processes payment data directly. -
Deutsche Bank – code
Selects flow based on 3DS and payment type (card or direct debit).
Key differences from tokenization‑first:
- Single API call – No separate token step
- No token storage – No token management required
- Immediate processing –
get_request_body()handles payment data directly
All implement the same ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> pattern.
Integrate a New Connector
Integrating a connector is mainly an API integration task. You'll define request and response types and implement required traits.
This section covers card payments via Billwerk. Review the API reference and test APIs before starting. You can leverage these examples for your connector of choice.
1. Build Payment Request and Response from JSON Schema
To generate Rust types from your connector’s OpenAPI or JSON schema, you’ll need to install the OpenAPI Generator.
Example (macOS using Homebrew):
brew install openapi-generator
💡 Note:
On Linux, you can install OpenAPI Generator usingapt,snap, or by downloading the JAR from the official site.
On Windows, use Scoop or manually download the JAR file.
2. Download the OpenAPI Specification from your connector
First, obtain the OpenAPI specification from your payment processor's developer documentation. Most processors provide these specifications at standardized endpoints.
curl -o <ConnectorName>-openapi.json <schema-url>
Specific Example:
For Billwerk (using their sandbox environment):
curl -o billwerk-openapi.json https://sandbox.billwerk.com/swagger/v1/swagger.json
For other connectors, check their developer documentation for similar endpoints like:
/swagger/v1/swagger.json/openapi.json/api-docs
After running the complete command, you'll have:
crates/hyperswitch_connectors/src/connectors/{CONNECTORNAME}/temp.rs
This single file contains all the Rust structs and types generated from your payment processor's OpenAPI specification.
The generated temp.rs file typically contains:
- Request structs: Data structures for API requests
- Response structs: Data structures for API responses
- Enum types: Status codes, payment methods, error types
- Nested objects: Complex data structures used within requests/responses
- Serde annotations: Serialization/deserialization attributes.
Otherwise, you can manually define it and create the crates/hyperswitch_connectors/src/connectors/{CONNECTOR_NAME}/temp.rs file. We highly recommend using the openapi-generator for ease.
Usage in Connector Development
You can then copy and adapt these generated structs into your connector's transformers.rs file, following the pattern shown in the connector integration documentation. The generated code serves as a starting point that you customize for your specific connector implementation needs.
3. Configure Required Environment Variables
Set up the necessary environment variables for the OpenAPI generation process:
Connector name (must match the name used in add_connector.sh script)
export CONNECTOR_NAME="ConnectorName"
Path to the downloaded OpenAPI specification
export SCHEMA_PATH="/absolute/path/to/your/connector-openapi.json"
Code Walkthrough
We'll walk through the transformer.rs file, and what needs to be implemented.
1. Converts Hyperswitch's internal payment data into your connector's API request format
This part of the code takes your internal representation of a payment request, pulls out the token, gathers all the customer and payment fields, and packages them into a clean, JSON-serializable struct ready to send to your connector of choice (in this case Billwerk). You'll have to implement the customer and payment fields, as necessary.
The code below extracts customer data and constructs a payment request:
//TODO: Fill the struct with respective fields
// Auth Struct
impl TryFrom<&ConnectorAuthType> for NadinebillwerkAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
match auth_type {
ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
api_key: api_key.to_owned(),
}),
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
}
}
}
Here's an implementation example with the Billwerk connector:
#[derive(Debug, Serialize)]
pub struct NadinebillwerkCustomerObject {
handle: Option<id_type::CustomerId>,
email: Option<Email>,
address: Option<Secret<String>>,
address2: Option<Secret<String>>,
city: Option<String>,
country: Option<common_enums::CountryAlpha2>,
first_name: Option<Secret<String>>,
last_name: Option<Secret<String>>,
}
impl TryFrom<&NadinebillwerkRouterData<&PaymentsAuthorizeRouterData>>
for NadinebillwerkPaymentsRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: &NadinebillwerkRouterData<&PaymentsAuthorizeRouterData>,
) -> Result<Self, Self::Error> {
if item.router_data.is_three_ds() {
return Err(errors::ConnectorError::NotImplemented(
"Three_ds payments through Billwerk".to_string(),
)
.into());
};
let source = match item.router_data.get_payment_method_token()? {
PaymentMethodToken::Token(pm_token) => Ok(pm_token),
_ => Err(errors::ConnectorError::MissingRequiredField {
field_name: "payment_method_token",
}),
}?;
Ok(Self {
handle: item.router_data.connector_request_reference_id.clone(),
amount: item.amount,
source,
currency: item.router_data.request.currency,
customer: NadinebillwerkCustomerObject {
handle: item.router_data.customer_id.clone(),
email: item.router_data.request.email.clone(),
address: item.router_data.get_optional_billing_line1(),
address2: item.router_data.get_optional_billing_line2(),
city: item.router_data.get_optional_billing_city(),
country: item.router_data.get_optional_billing_country(),
first_name: item.router_data.get_optional_billing_first_name(),
last_name: item.router_data.get_optional_billing_last_name(),
},
metadata: item.router_data.request.metadata.clone().map(Into::into),
settle: item.router_data.request.is_auto_capture()?,
})
}
}
- Handle Response Mapping
Response mapping is a critical component of connector implementation that translates payment processor–specific statuses into Hyperswitch’s standardized internal representation. This ensures consistent payment state management across all integrated payment processors.
Define Payment Status Enum
Create an enum that represents all possible payment statuses returned by your payment processor’s API. This enum should match the exact status values specified in your connector’s API documentation.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BillwerkPaymentState {
Created,
Authorized,
Pending,
Settled,
Failed,
Cancelled,
}
The enum uses #[serde(rename_all = "lowercase")] to automatically handle JSON serialization/deserialization in the connector’s expected format.
Implement Status Conversion
Implement From for Hyperswitch’s AttemptStatus enum. Below is an example implementation:
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum BillwerkPaymentState {
Created,
Authorized,
Pending,
Settled,
Failed,
Cancelled,
}
impl From<BillwerkPaymentState> for enums::AttemptStatus {
fn from(item: BillwerkPaymentState) -> Self {
match item {
BillwerkPaymentState::Created | BillwerkPaymentState::Pending => Self::Pending,
BillwerkPaymentState::Authorized => Self::Authorized,
BillwerkPaymentState::Settled => Self::Charged,
BillwerkPaymentState::Failed => Self::Failure,
BillwerkPaymentState::Cancelled => Self::Voided,
}
}
}
| Connector Status | Hyperswitch Status | Description |
|---|---|---|
Created, Pending |
AttemptStatus::Pending |
Payment is being processed |
Authorized |
AttemptStatus::Authorized |
Payment authorized, awaiting capture |
Settled |
AttemptStatus::Charged |
Payment successfully completed |
Failed |
AttemptStatus::Failure |
Payment failed or was declined |
Cancelled |
AttemptStatus::Voided |
Payment was cancelled/voided |
Note: Default status should be
Pending. Only explicit success or failure from the connector should mark the payment asChargedorFailure.
- Mapping Billwerk API Responses (or any PSPs) to Hyperswitch Internal Specification
Billwerk, like most payment service providers (PSPs), has its own proprietary API response format with custom fields, naming conventions, and nested structures. However, Hyperswitch is designed to be connector-agnostic: it expects all connectors to normalize external data into a consistent internal format, so it can process payments uniformly across all supported PSPs.
The response struct acts as the translator between these two systems. This process ensures that regardless of which connector you're using, Hyperswitch can process payment responses consistently.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BillwerkPaymentsResponse {
state: BillwerkPaymentState,
handle: String,
error: Option<String>,
error_state: Option<String>,
}
Key Fields Explained:
- state: Payment status using the enum we defined earlier
- handle: Billwerk's unique transaction identifier
- error & error_state: Optional error information for failure scenarios
The try_from function converts connector-specific, like Billwerk, response data into Hyperswitch's standardized format:
impl<F, T> TryFrom<ResponseRouterData<F, BillwerkPaymentsResponse, T, PaymentsResponseData>>
for RouterData<F, T, PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: ResponseRouterData<F, BillwerkPaymentsResponse, T, PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let error_response = if item.response.error.is_some() || item.response.error_state.is_some()
{
Some(ErrorResponse {
code: item
.response
.error_state
.clone()
.unwrap_or(NO_ERROR_CODE.to_string()),
message: item
.response
.error_state
.unwrap_or(NO_ERROR_MESSAGE.to_string()),
reason: item.response.error,
status_code: item.http_code,
attempt_status: None,
connector_transaction_id: Some(item.response.handle.clone()),
})
} else {
None
};
let payments_response = PaymentsResponseData::TransactionResponse {
resource_id: ResponseId::ConnectorTransactionId(item.response.handle.clone()),
redirection_data: Box::new(None),
mandate_reference: Box::new(None),
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: Some(item.response.handle),
incremental_authorization_allowed: None,
charges: None,
};
Ok(Self {
status: enums::AttemptStatus::from(item.response.state),
response: error_response.map_or_else(|| Ok(payments_response), Err),
..item.data
})
}
}
Transformation Logic:
- Error Handling: Checks for error conditions first and creates appropriate error responses
- Status Mapping: Converts BillwerkPaymentState to standardized AttemptStatus using our enum mapping
- Data Extraction: Maps PSP-specific fields to Hyperswitch's PaymentsResponseData structure
- Metadata Preservation: Ensures important transaction details are retained
Critical Response Fields
The transformation populates these essential Hyperswitch fields:
- resource_id: Maps to connector transaction ID for future operations
- connector_response_reference_id: Preserves PSP's reference for dashboard linking
- status: Standardized payment status for consistent processing
- redirection_data: Handles 3DS or other redirect flows
- network_txn_id: Captures network-level transaction identifiers
Field Mapping Patterns:
Each critical response field requires specific implementation patterns to ensure consistent behavior across all Hyperswitch connectors.
- connector_request_reference_id: This field carries the merchant’s reference ID and is populated during request construction. It is sent to the PSP to support end-to-end transaction traceability.
reference: item.router_data.connector_request_reference_id.clone(),
- connector_response_reference_id: Stores the payment processor’s transaction reference and is used for downstream reconciliation and dashboard visibility. Prefer the PSP's designated reference field if available; otherwise, fall back to the transaction ID. This ensures accurate linkage across merchant dashboards, support tools, and internal systems.
connector_response_reference_id: item.response.reference.or(Some(item.response.id)),
- resource_id: Defines the primary resource identifier used for subsequent operations such as captures, refunds, and syncs. Typically sourced from the connector’s transaction ID. If the transaction ID is unavailable, use ResponseId::NoResponseId as a fallback to preserve type safety.
`resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()),
- redirection_data: Captures redirection details required for authentication flows such as 3DS. If the connector provides a redirect URL, populate this field accordingly. For advanced flows involving form submissions, construct a
RedirectForm::Formusing the target endpoint, HTTP method, and form fields.
let redirection_data = item.response.links.redirect.map(|href| {
services::RedirectForm::from((href.redirection_url, services::Method::Get))
});
- network_txn_id: Stores the transaction identifier issued by the underlying payment network (e.g., Visa, Mastercard). This field is optional but highly useful for advanced reconciliation, chargeback handling, and network-level dispute resolution—especially when the network ID differs from the PSP’s transaction ID.
network_txn_id: item.response.network_transaction_id.clone(),
- Error Handling in Hyperswitch Connectors
Hyperswitch connectors implement a structured error-handling mechanism that categorizes HTTP error responses by type. By distinguishing between client-side errors (4xx) and server-side errors (5xx), the system enables more precise handling strategies tailored to the source of the failure.
Error Response Structure
Billwerk defines its error response format to capture failure information from API calls. You can find this in the transformer.rs file:
#[derive(Debug, Serialize, Deserialize)]
pub struct BillwerkErrorResponse {
pub code: Option<i32>,
pub error: String,
pub message: Option<String>,
}
- code: Optional integer error code from Billwerk
- error: Required string describing the error
- message: Optional additional error messagecode: Optional integer error code from Billwerk
- error: Required string describing the error message: Optional additional error message
Error Handling Methods
Hyperswitch uses separate methods for different HTTP error types:
- 4xx Client Errors:
get_error_responsehandles authentication failures, validation errors, and malformed requests. - 5xx Server Errors:
get_5xx_error_responsehandles internal server errors with potential retry logic.
Both methods delegate to build_error_response for consistent processing.
Error Processing Flow
The build_error_response struct serves as the intermediate data structure that bridges Billwerk's API error format and Hyperswitch's standardized error format by taking the BillwerkErrorResponse struct as input:
fn build_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: BillwerkErrorResponse = res
.response
.parse_struct("BillwerkErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
Ok(ErrorResponse {
status_code: res.status_code,
code: response
.code
.map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()),
message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()),
reason: Some(response.error),
attempt_status: None,
connector_transaction_id: None,
network_advice_code: None,
network_decline_code: None,
network_error_message: None,
})
}
}
The method performs these key operations:
-
Parses the HTTP response - Deserializes the raw HTTP response into a BillwerkErrorResponse struct using
parse_struct("BillwerkErrorResponse") -
Logs the response - Records the connector response for debugging via
event_builderandrouter_env::logger::info! -
Transforms error format - Maps Billwerk's error fields to Hyperswitch's standardized
ErrorResponsestructure with appropriate fallbacks: -
- Uses
response.codemaps tocode(withNO_ERROR_CODE fallback)
- Uses
-
- Uses
response.messagemaps tomessage(withNO_ERROR_MESSAGE fallback)
- Uses
-
- Maps
response.errorto thereasonfield
- Maps
Note
When the connector provides only a single error message field, populate both the
messageandreasonfields in theErrorResponsewith the same value. Themessagefield is used for smart retries logic, while thereasonfield is displayed on the Hyperswitch dashboard.
Automatic Error Routing
Hyperswitch's core API automatically routes errors based on HTTP status codes. You can find the details here: crates/router/src/services/api.rs.
- 4xx →
get_error_response - 5xx →
get_5xx_error_response - 2xx →
handle_response
Integration Pattern
The BillwerkErrorResponse struct serves as the intermediate data structure that bridges Billwerk's API error format and Hyperswitch's internal error representation. The method essentially consumes the struct and produces Hyperswitch's standardized error format. All connectors implement a similar pattern to ensure uniform error handling.
Implementing the Connector Interface
The connector interface implementation follows an architectural pattern that separates concerns between data transformation and interface compliance.
-
transformers.rs- This file is generated fromadd_connector.shand defines the data structures and conversion logic for PSP-specific formats. This is where most of your custom connector implementation work happens. -
mod.rs- This file implements the standardized Hyperswitch connector interface using the transformers.
The mod.rs Implementation Pattern
The file creates the bridge between the data transformation logic (defined in transformers.rs) and the connector interface requirements. It serves as the main connector implementation file that brings together all the components defined in the transformers module and implements all the required traits for payment processing. Looking at the connector template structure connector-template/mod.rs:54-67, you can see how it:
- Imports the transformers module - Brings in your PSP-specific types and conversion logic
use transformers as {{project-name | downcase}};
- Creates the main connector struct - A struct named after your connector that holds the implementation
#[derive(Clone)]
pub struct {{project-name | downcase | pascal_case}} {
amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync)
}
impl {{project-name | downcase | pascal_case}} {
pub fn new() -> &'static Self {
&Self {
amount_converter: &StringMinorUnitForConnector
}
}
}
- Implements required traits - Provides the standardized methods Hyperswitch expects
impl ConnectorCommon for {{project-name | downcase | pascal_case}} {
fn id(&self) -> &'static str {
"{{project-name | downcase}}"
}
fn get_currency_unit(&self) -> api::CurrencyUnit {
todo!()
// TODO! Check connector documentation, on which unit they are processing the currency.
// If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor,
// if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base
}
fn common_get_content_type(&self) -> &'static str {
"application/json"
}
fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
connectors.{{project-name}}.base_url.as_ref()
}
fn get_auth_header(&self, auth_type:&ConnectorAuthType)-> CustomResult<Vec<(String,masking::Maskable<String>)>,errors::ConnectorError> {
let auth = {{project-name | downcase}}::{{project-name | downcase | pascal_case}}AuthType::try_from(auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key.expose().into_masked())])
}
fn build_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: {{project-name | downcase}}::{{project-name | downcase | pascal_case}}ErrorResponse = res
.response
.parse_struct("{{project-name | downcase | pascal_case}}ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
Ok(ErrorResponse {
status_code: res.status_code,
code: response.code,
message: response.message,
reason: response.reason,
attempt_status: None,
connector_transaction_id: None,
network_advice_code: None,
network_decline_code: None,
network_error_message: None,
})
}
}
ConnectorCommon: The Foundation Trait
The ConnectorCommon trait defines the standardized interface required by Hyperswitch (as outlined in crates/hyperswitch_interfaces/src/api.rs and acts as the bridge to your PSP-specific logic in transformers.rs. The connector-template/mod.rs file implements this trait using the data types and transformation functions from transformers.rs. This allows Hyperswitch to interact with your connector in a consistent, processor-agnostic manner. Every connector must implement the ConnectorCommon trait, which provides essential connector properties:
Core Methods You'll Implement
id()- Your connector's unique identifier
fn id(&self) -> &'static str {
"Billwerk"
}
get_currency_unit()- Whether you handle amounts in base units (dollars) or minor units (cents).
fn get_currency_unit(&self) -> api::CurrencyUnit {
api::CurrencyUnit::Minor
}
base_url()- This fetches your PSP's API endpoint
fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
connectors.billwerk.base_url.as_ref()
}
get_auth_header()- How to authenticate with your PSP
fn get_auth_header(
&self,
auth_type: &ConnectorAuthType,
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
let auth = BillwerkAuthType::try_from(auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek()));
Ok(vec![(
headers::AUTHORIZATION.to_string(),
format!("Basic {encoded_api_key}").into_masked(),
)])
}
build_error_response()- How to transform your PSP's errors into Hyperswitch's format
fn build_error_response(
&self,
res: Response,
event_builder: Option<&mut ConnectorEvent>,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: BillwerkErrorResponse = res
.response
.parse_struct("BillwerkErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
event_builder.map(|i| i.set_response_body(&response));
router_env::logger::info!(connector_response=?response);
Ok(ErrorResponse {
status_code: res.status_code,
code: response
.code
.map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()),
message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()),
reason: Some(response.error),
attempt_status: None,
connector_transaction_id: None,
})
}
ConnectorIntegration - The Payment Flow Orchestrator
The ConnectorIntegration trait serves as the central coordinator that bridges three key files in Hyperswitch's connector architecture:
-
Defined in
api.rs
crates/hyperswitch_interfaces/src/api.rs:150–153
Provides the standardized interface contracts for connector integration. -
Implemented in
mod.rs
Each connector’s main file (mod.rs) implements the trait methods for specific payment flows like authorize, capture, refund, etc. You can see how the Tsys connector implements ConnectorIntegration -
Uses types from
transformers.rs
Contains PSP-specific request/response structs andTryFromimplementations that convert between Hyperswitch's internalRouterDataformat and the PSP's API format. This is where most connector-specific logic lives.
This orchestration enables seamless translation between Hyperswitch’s internal data structures and each payment service provider’s unique API requirements.
Method-by-Method Breakdown
Request/Response Flow
These methods work together in sequence:
get_url()andget_headers()prepare the endpoint and authenticationget_request_body()transforms Hyperswitch data using transformers.rsbuild_request()assembles the complete HTTP requesthandle_response()processes the PSP response back to Hyperswitch formatget_error_response()handles any error conditions
Here are more examples around these methods in the Billwerk connector:
-
get_url()
Constructs API endpoints by combining base URLs (fromConnectorCommon) with specific paths. In the Billwerk connector, it reads the connector’s base URL from config and appends the tokenization path. Here's 1 example. -
get_headers()
Here's an example of get_headers. It delegates tobuild_headers()across all connector implementations. -
get_request_body()
Uses theTryFromimplementations in billwerk.rs. It creates the connector request viaBillwerkTokenRequest::try_from(req)?to transform the tokenization router data and it returns asRequestContent:by wrapping it in a JSON viaRequestContent::Json(Box::new(connector_req)) -
build_request()
Orchestratesget_url(),get_headers(), andget_request_body()to assemble the complete HTTP request via aRequestBuilder. For example, you can review the Billwerk connector'sbuild_request()implementation. -
handle_response()
You can see an example of this here:billwerk.rs. In this example, it parses the raw response intoBillwerkTokenResponseusingres.response.parse_struct(), logs the response with anevent_builder.map(|i| i.set_response_body(&response)), finally it transforms back toRouterDatausingRouterData::try_from(ResponseRouterData {...}). -
get_error_response()Here's an example of get_error_response inbillewerk.rs. It delegates tobuild_error_response()from theConnectorCommontrait, providing uniform handling for all connector 4xx errors.
ConnectorCommonExt - Generic Helper Methods
The ConnectorCommonExt trait serves as an extension layer for the core ConnectorCommon trait, providing generic methods that work across different payment flows. It'requires both ConnectorCommon and ConnectorIntegration to be implemented.
Connector Traits Overview
Payment
Includes several sub-traits and represents general payment functionality.
- Defined in:
crates/hyperswitch_interfaces/src/types.rs:11-16 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:70
PaymentAuthorize
Extends the api::ConnectorIntegration trait with types for payment authorization.
- Flow type defined in:
crates/router/src/types.rs:39 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:74
PaymentCapture
Extends the api::ConnectorIntegration trait with types for manual capture of a previously authorized payment.
- Flow type defined in:
crates/router/src/types.rs:39 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:76
PaymentSync
Extends the api::ConnectorIntegration trait with types for retrieving or synchronizing payment status.
- Flow type defined in:
crates/router/src/types.rs:41 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:75
Refund
Includes several sub-traits and represents general refund functionality.
- Defined in:
crates/hyperswitch_interfaces/src/types.rs:17 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:78
RefundExecute
Extends the api::ConnectorIntegration trait with types for creating a refund.
- Flow type defined in:
crates/router/src/types.rs:44 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:79
RefundSync
Extends the api::ConnectorIntegration trait with types for retrieving or synchronizing a refund.
- Flow type defined in:
crates/router/src/types.rs:44 - Example implementation:
crates/hyperswitch_connectors/src/connectors/novalnet.rs:80
Connector Required Fields Configuration
The file crates/payment_methods/src/configs/payment_connector_required_fields.rs is the central configuration file that defines required fields for each connector and payment-method combination.
Example: Billwerk Required Fields
Based on the required-fields configuration, Billwerk requires only basic card details for card payments. Please see payment_connector_required_fields.rs:1271.
Specifically, Billwerk requires:
- Card number
- Card expiry month
- Card expiry year
- Card CVC
This is defined using the card_basic() helper (see payment_connector_required_fields.rs:876–884), which specifies these four essential card fields as RequiredField enum variants.
Comparison with Other Connectors
Billwerk has relatively minimal requirements compared to other connectors. For example:
- Bank of America requires card details plus email, full name, and complete billing address (see
payment_connector_required_fields.rs:1256–1262). - Cybersource requires card details, billing email, full name, and billing address (see
payment_connector_required_fields.rs:1288–1294).
Please review the file for your specific connector requirements.
Derive Traits
The derive traits are standard Rust traits that are automatically implemented:
- Debug: Standard Rust trait for debug formatting. It's automatically derived on connector structs like
crates/hyperswitch_connectors/src/connectors/coinbase.rs:52 - Clone: Standard Rust trait for cloning. It's implemented on connector structs like
crates/hyperswitch_connectors/src/connectors/novalnet.rs:57 - Copy: Standard Rust trait for copy semantics. It's used where applicable for simple data structures
These traits work together to provide a complete payment processing interface, with each trait extending ConnectorIntegration with specific type parameters for different operations.
Connector utility functions
Hyperswitch provides a set of standardized utility functions to streamline data extraction, validation, and formatting across all payment connectors. These are primarily defined in:
Key Utilities and Traits
RouterData Trait
Provides helper methods to extract billing and browser data:
get_billing_country()– Retrieves the billing countryget_billing_email()– Gets the customer email from billing dataget_billing_full_name()– Extracts full nameget_browser_info()– Parses browser details for 3DSis_three_ds()– Checks if 3DS is requiredis_auto_capture()– Determines if auto-capture is enabled
CardData Trait
Handles card-specific formatting and parsing:
get_expiry_date_as_yyyymm()– Formats expiry as YYYYMMget_expiry_date_as_mmyyyy()– Formats expiry as MMYYYYget_card_expiry_year_2_digit()– Gets 2-digit expiry yearget_card_issuer()– Returns card brand (Visa, Mastercard, etc.)get_cardholder_name()– Extracts name on card
Wallet Data
Utility for processing digital wallet tokens:
let json_wallet_data: CheckoutGooglePayData = wallet_data.get_wallet_token_as_json()?;
Real-World Usage Examples
-
PayPal Connector:
get_expiry_date_as_yyyymm()is used for tokenization and authorization -
Bambora Connector:
get_browser_info()is used to enables 3DS andis_auto_capture()is used to check capture behavior -
Trustpay Connector: Uses extensive browser info usage for 3DS validation flows
Error Handling & Validation
missing_field_err()– Commonly used across connectors for standardized error reporting
Connector Configuration for Control Center Integration
This guide helps developers integrate custom connectors with the Hyperswitch Control Center by configuring connector settings and building the required WebAssembly components.
Prerequisites
Install the WebAssembly build tool:
cargo install wasm-pack
Step 1: Configure Your Connector
Add your connector configuration to the development environment file
The connector configuration system does support multiple environments as you mentioned. The system automatically selects the appropriate configuration file based on feature flags:
- Production: crates/connector_configs/toml/production.toml
- Sandbox: crates/connector_configs/toml/sandbox.toml
- Development: crates/connector_configs/toml/development.toml (default)
# Example: Adding a new connector configuration
[your_connector_name]
[your_connector_name.connector_auth.HeaderKey]
api_key = "Your_API_Key_Here"
# Optional: Add additional connector-specific settings
[your_connector_name.connector_webhook_details]
merchant_secret = "webhook_secret"
Step 2: Build WebAssembly Components
The Control Center requires WebAssembly files for connector integration. Build them using:
wasm-pack build \
--target web \
--out-dir /path/to/hyperswitch-control-center/public/hyperswitch/wasm \
--out-name euclid \
/path/to/hyperswitch/crates/euclid_wasm \
-- --features dummy_connector
-
Replace
/path/to/hyperswitch-control-centerwith your Control Center installation directory -
Replace
/path/to/hyperswitchwith your Hyperswitch repository root
The build process uses the euclid_wasm crate, which provides WebAssembly bindings for connector configuration and routing logic.
Step 3: Verify Integration
The WebAssembly build includes connector configuration functions that the Control Center uses to retrieve connector settings dynamically.
You can also use the Makefile target for convenience:
make euclid-wasm
This target is defined in the Makefile:86-87 and handles the build process with appropriate feature flags.
Configuration Features
The connector configuration system supports:
-
Environment-specific configs: Development, sandbox, and production configurations
-
Authentication methods: HeaderKey, BodyKey, SignatureKey, etc.
-
Webhook configuration: For handling asynchronous payment notifications
-
Payment method support: Defining which payment methods your connector supports
Troubleshooting
If the build fails, ensure:
- Your connector is properly registered in the connector enum
- The WebAssembly target is installed:
rustup target add wasm32-unknown-unknown - All required features are enabled in your connector's
Cargo.toml - The configuration system automatically loads the appropriate environment settings based on compile-time features, ensuring your connector works correctly across different deployment environments.
Control Center Frontend Integration
This section covers integrating your new connector with the Hyperswitch Control Center's frontend interface, enabling merchants to configure and manage your connector through the dashboard.
Update Frontend Connector Configuration
- Add Connector to Type Definitions
Update the connector enum in the Control Center's type definitions
type processorTypes =
| BREADPAY
| BLUECODE
| YourNewConnector // Add your connector here at the bottom
Update Connector Utilities
Modify the connector utilities to include your new connector.
// Add to connector list at the bottom
let connectorList: array<connectorTypes> = [
....
Processors(BREADPAY),
Processors(BLUECODE),
Processors(YourNewConnector)
]
// Add display name mapping at the bottom
let getConnectorNameString = (connectorName: connectorName) =>
switch connectorName {
| BREADPAY => "breadpay"
| BLUECODE => "bluecode"
| YourNewConnector => "Your New Connector"
}
// Add connector description at the bottom
let getProcessorInfo = (connector: ConnectorTypes.processorTypes) => {
switch connectorName {
| BREADPAY => breadpayInfo
| BLUECODE => bluecodeInfo
| YourNewConnector => YourNewConnectorInfo
}
After bluecodeinfo definition, add the definition of your connector in a similar format:
let YourNewConnectorInfo = {
description: "Info for the connector.",
}
Add Connector Icon
- Prepare Icon Asset
- Create an SVG icon for your connector
- Name it in uppercase format: YOURCONNECTOR.SVG
- Ensure the icon follows the design guidelines (typically 24x24px or 32x32px)
- Add to Assets Directory Place your icon in the Control Center's gateway assets folder:
public/
└── hyperswitch/
└── Gateway/
└── YOURCONNECTOR.SVG
The icon will be automatically loaded by the frontend based on the connector name mapping.
Test the Connector Integration
After successfully creating your connector using the add_connector.sh script, you need to configure authentication credentials and test the integration. This section covers the complete testing setup process.
Authentication Setup
- Obtain PSP Credentials
First, obtain sandbox/UAT API credentials from your payment service provider. These are typically available through their developer portal or dashboard.
- Create Authentication File
Copy the sample authentication template and create your credentials file:
cp crates/router/tests/connectors/sample_auth.toml auth.toml
The sample file crates/router/tests/connectors/sample_auth.toml contains templates for all supported connectors. Edit your auth.toml file to include your connector's credentials:
Example for the Billwerk connector
[billewerk]
api_key = "sk_test_your_actual_billwerk_test_key_here"
- Configure Environment Variables
Set the path to your authentication file:
export CONNECTOR_AUTH_FILE_PATH="/absolute/path/to/your/auth.toml"
- Use
direnvfor Environment Management (recommended)
For better environment variable management, use direnv with a .envrc file in the cypress-tests directory.
- Create
.envrcin thecypress-testsdirectory
cd cypress-tests
Create a .envrc file with the following content:
export CONNECTOR_AUTH_FILE_PATH="/absolute/path/to/your/auth.toml"
export CYPRESS_CONNECTOR="your_connector_name"
export CYPRESS_BASEURL="http://localhost:8080"
export CYPRESS_ADMINAPIKEY="test_admin"
export DEBUG=cypress:cli
- Allow
direnvto load the variables inside thecypress-testsdirectory:
direnv allow
Test the Connector Integration
- Start the Hyperswitch Router Service locally:
cargo r
- Verify Server Health
curl --head --request GET 'http://localhost:8080/health'
Detailed health check
curl --request GET 'http://localhost:8080/health/ready'
- Run Connector Tests for Your Connector
cargo test --package router --test connectors -- your_connector_name --test-threads=1
The authentication system will load your credentials from the specified path and use them for testing.
⚠️ Important Notes
- Never commit
auth.toml– It contains sensitive credentials and should never be added to version control- Use absolute paths – This avoids issues when running tests from different directories
- Populate with real test credentials – Replace the placeholder values from the sample file with actual sandbox/UAT credentials from your payment processors. Please don't use production credentials.
- Rotate credentials regularly – Update test keys periodically for security.