add_connector.md
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:
ConnectorIntegration trait for both standard auth and tokenization-first flowsBy the end, you’ll learn how to create a fully functional, production-ready connector—from blank slate to live in the Control-Center.
This guide will walk you through your environment setup and configuration.
git clone [email protected]:juspay/hyperswitch.git
cd hyperswitch
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 cargo-generate for creating project templates:
cargo install cargo-generate
If you've completed the setup, you should now have:
cargo-generatediesel_clijust command runnerCompile and run the application using cargo:
cargo run
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:bashtest result: FAILED. 0 passed; 20 failed; 0 ignored; 0 measured; 1759 filtered out; finished in 0.10sYou can also ignore GRPC errors too.
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:
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.
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.
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
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 formatAs you build your connector, you’ll encounter different payment flow patterns.
This section gives you:
For full details, see Connector Payment Flow documentation or ask us in Slack.
| 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 |
Each flow type corresponds to specific request/response data structures and connector integration patterns. All flows follow a standardized pattern with associated:
PaymentsAuthorizeData)PaymentsResponseData)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.
Many connectors skip tokenization and send payment data directly in the authorization request.
Authorize.net – code
Builds CreateTransactionRequest directly from payment data in get_request_body().
Helcim – code
Chooses purchase (auto‑capture) or preauth endpoint in get_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:
get_request_body() handles payment data directlyAll implement the same ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> pattern.
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.
To generate Rust types from your connector’s OpenAPI or JSON schema, you’ll need to install the OpenAPI Generator.
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.
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-docsAfter 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:
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.
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.
Set up the necessary environment variables for the OpenAPI generation process:
export CONNECTOR_NAME="ConnectorName"
export SCHEMA_PATH="/absolute/path/to/your/connector-openapi.json"
We'll walk through the transformer.rs file, and what needs to be implemented.
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()?,
})
}
}
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 <ConnectorStatus> 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.
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:
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
})
}
}
The transformation populates these essential Hyperswitch fields:
Each critical response field requires specific implementation patterns to ensure consistent behavior across all Hyperswitch connectors.
reference: item.router_data.connector_request_reference_id.clone(),
connector_response_reference_id: item.response.reference.or(Some(item.response.id)),
`resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()),
RedirectForm::Form using 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: item.response.network_transaction_id.clone(),
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>,
}
Error Handling Methods
Hyperswitch uses separate methods for different HTTP error types:
get_error_response handles authentication failures, validation errors, and malformed requests.get_5xx_error_response handles 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_builder and router_env::logger::info!
Transforms error format - Maps Billwerk's error fields to Hyperswitch's standardized ErrorResponse structure with appropriate fallbacks:
response.code maps to code (with NO_ERROR_CODE fallback)response.message maps to message (with NO_ERROR_MESSAGE fallback)response.error to the reason field[!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.
Hyperswitch's core API automatically routes errors based on HTTP status codes. You can find the details here: crates/router/src/services/api.rs.
get_error_responseget_5xx_error_responsehandle_responseThe 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.
The connector interface implementation follows an architectural pattern that separates concerns between data transformation and interface compliance.
transformers.rs - This file is generated from add_connector.sh and 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.
mod.rs Implementation PatternThe 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:
use transformers as {{project-name | downcase}};
#[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
}
}
}
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,hyperswitch_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,
})
}
}
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:
id() - Your connector's unique identifierfn 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, hyperswitch_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 formatfn 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 OrchestratorThe 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 and TryFrom implementations that convert between Hyperswitch's internal RouterData format 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.
These methods work together in sequence:
get_url() and get_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 conditionsHere are more examples around these methods in the Billwerk connector:
get_url()
Constructs API endpoints by combining base URLs (from ConnectorCommon) 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 to build_headers() across all connector implementations.
get_request_body()
Uses the TryFrom implementations in billwerk.rs. It creates the connector request via BillwerkTokenRequest::try_from(req)? to transform the tokenization router data and it
returns as RequestContent: by wrapping it in a JSON via RequestContent::Json(Box::new(connector_req))
build_request()
Orchestrates get_url(), get_headers(), and get_request_body() to assemble the complete HTTP request via a RequestBuilder. For example, you can review the Billwerk connector's build_request() implementation.
handle_response()
You can see an example of this here: billwerk.rs. In this example, it parses the raw response into BillwerkTokenResponse using res.response.parse_struct(), logs the response with an event_builder.map(|i| i.set_response_body(&response)), finally it
transforms back to RouterData using RouterData::try_from(ResponseRouterData {...}) .
get_error_response()
Here's an example of get_error_response in billewerk.rs. It delegates to build_error_response() from the ConnectorCommon trait, providing uniform handling for all connector 4xx errors.
ConnectorCommonExt - Generic Helper MethodsThe 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.
PaymentIncludes several sub-traits and represents general payment functionality.
crates/hyperswitch_interfaces/src/types.rs:11-16crates/hyperswitch_connectors/src/connectors/novalnet.rs:70PaymentAuthorizeExtends the api::ConnectorIntegration trait with types for payment authorization.
crates/router/src/types.rs:39crates/hyperswitch_connectors/src/connectors/novalnet.rs:74PaymentCaptureExtends the api::ConnectorIntegration trait with types for manual capture of a previously authorized payment.
crates/router/src/types.rs:39crates/hyperswitch_connectors/src/connectors/novalnet.rs:76PaymentSyncExtends the api::ConnectorIntegration trait with types for retrieving or synchronizing payment status.
crates/router/src/types.rs:41crates/hyperswitch_connectors/src/connectors/novalnet.rs:75RefundIncludes several sub-traits and represents general refund functionality.
crates/hyperswitch_interfaces/src/types.rs:17crates/hyperswitch_connectors/src/connectors/novalnet.rs:78RefundExecuteExtends the api::ConnectorIntegration trait with types for creating a refund.
crates/router/src/types.rs:44crates/hyperswitch_connectors/src/connectors/novalnet.rs:79RefundSyncExtends the api::ConnectorIntegration trait with types for retrieving or synchronizing a refund.
crates/router/src/types.rs:44crates/hyperswitch_connectors/src/connectors/novalnet.rs:80The 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.
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:
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.
Billwerk has relatively minimal requirements compared to other connectors. For example:
payment_connector_required_fields.rs:1256–1262).payment_connector_required_fields.rs:1288–1294).Please review the file for your specific connector requirements.
The derive traits are standard Rust traits that are automatically implemented:
crates/hyperswitch_connectors/src/connectors/coinbase.rs:52crates/hyperswitch_connectors/src/connectors/novalnet.rs:57These traits work together to provide a complete payment processing interface, with each trait extending ConnectorIntegration with specific type parameters for different operations.
Hyperswitch provides a set of standardized utility functions to streamline data extraction, validation, and formatting across all payment connectors. These are primarily defined in:
RouterData TraitProvides 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 enabledCardData TraitHandles 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 cardUtility for processing digital wallet tokens:
let json_wallet_data: CheckoutGooglePayData = wallet_data.get_wallet_token_as_json()?;
PayPal Connector: get_expiry_date_as_yyyymm() is used for tokenization and authorization
Bambora Connector: get_browser_info() is used to enables 3DS and is_auto_capture() is used to check capture behavior
Trustpay Connector: Uses extensive browser info usage for 3DS validation flows
missing_field_err() – Commonly used across connectors for standardized error reportingThis guide helps developers integrate custom connectors with the Hyperswitch Control Center by configuring connector settings and building the required WebAssembly components.
Install the WebAssembly build tool:
cargo install wasm-pack
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:
# 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"
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-center with your Control Center installation directory
Replace /path/to/hyperswitch with your Hyperswitch repository root
The build process uses the euclid_wasm crate, which provides WebAssembly bindings for connector configuration and routing logic.
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.
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
If the build fails, ensure:
rustup target add wasm32-unknown-unknownCargo.tomlThis 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 the connector enum in the Control Center's type definitions
type processorTypes =
| BREADPAY
| BLUECODE
| YourNewConnector // Add your connector here at the bottom
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.",
}
public/
└── hyperswitch/
└── Gateway/
└── YOURCONNECTOR.SVG
The icon will be automatically loaded by the frontend based on the connector name mapping.
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.
First, obtain sandbox/UAT API credentials from your payment service provider. These are typically available through their developer portal or dashboard.
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"
Set the path to your authentication file:
export CONNECTOR_AUTH_FILE_PATH="/absolute/path/to/your/auth.toml"
direnv for Environment Management (recommended)For better environment variable management, use direnv with a .envrc file in the cypress-tests directory.
.envrc in the cypress-tests directorycd 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
direnv to load the variables inside the cypress-tests directory:direnv allow
cargo r
curl --head --request GET 'http://localhost:8080/health'
Detailed health check
curl --request GET 'http://localhost:8080/health/ready'
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.