Integrating Interswitch VAS Authentication with Spring Boot
Backend • 5 min read

Integrating Interswitch VAS Authentication with Spring Boot

Julius Fasema

Author

Build a Spring Boot service that authenticates with Interswitch VAS using OAuth 2.0 Client Credentials

Modern applications increasingly depend on external APIs for services such as airtime, data subscriptions, bill payments and other value-added services (VAS). Instead of implementing these services from scratch, developers can integrate with an established VAS platform and expose those capabilities through their own applications.

In this practical tutorial, we will build the authentication layer for an Interswitch VAS integration using Spring Boot.

The implementation uses Spring's RestTemplate to communicate with the Interswitch authentication endpoint, sends OAuth 2.0 client credentials, receives an access token and exposes our own REST endpoint that can be used to initiate authentication.

The authentication service will eventually become the foundation for calling other Interswitch VAS APIs.

Note: The credentials shown in this article are placeholders. Never publish real Interswitch client credentials, merchant credentials or secrets in source code.

What We Are Building

The application will have the following flow:

The important point is that our application does not directly expose the Interswitch credentials to the client. Instead, Spring Boot keeps the credentials on the server and uses them to obtain an access token.

Step 1: Create the Spring Boot Project

The first step is to scaffold a Spring Boot application from Spring Initializr.

Visit:

www.spring.io

For this project, select:

  • Project: Maven
  • Language: Java
  • Spring Boot: Current stable version
  • Packaging: Jar
  • Java: 17 or later

Add the dependencies required by your application. At minimum, the project needs Spring Web.

If the application already uses Lombok and OpenAPI/Swagger, those dependencies can also be included.

A typical Maven configuration might contain:

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

        <optional>true</optional>

    </dependency>

</dependencies>

After generating the project, open it in your preferred IDE.

 Step 2: Define the Project Structure

Rather than putting the entire integration into one large class, it is better to separate the responsibilities.

For our VAS integration, we create a dedicated vas package:

vas

── config

│   ── RestTemplateConfig.java

└── auth

│   ── InterswitchAuthService.java

└── controller

│ ── InterswitchAuthController.java

└── dto

    ── InterswitchTokenResponse.java

The responsibilities are:

config

Contains configuration classes required by the integration.

In this example, it contains the RestTemplate bean.

auth

Contains the authentication business logic.

InterswitchAuthService is responsible for communicating with the Interswitch authentication server.

controller

Contains REST endpoints exposed by our application.

InterswitchAuthController exposes an endpoint for testing and initiating authentication.

dto

Contains Java objects used to represent external API responses.

InterswitchTokenResponse maps the JSON returned by Interswitch.

Each operation can have its own service and DTOs without turning the project into a single massive class.

Step 3: Configure Interswitch Properties

Next, add the Interswitch configuration to application.properties.

# ==========================================

# INTERSWITCH VAS INTEGRATION

# ==========================================

interswitch.test.baseUrl=https://qa.interswitchng.com/quicktellerservice/api/v5

interswitch.live.baseUrl=https://orion.interswitchng.com/quicktellerservice/api/v5

interswitch.merchantCode=YOUR_MERCHANT_CODE

interswitch.payItemID=YOUR_PAY_ITEM_ID

interswitch.dataRef=YOUR_DATA_REFERENCE

interswitch.client.id=YOUR_CLIENT_ID

interswitch.client.secret=YOUR_CLIENT_SECRET

interswitch.terminalID=YOUR_TERMINAL_ID

interswitch.auth.url=https://passport-sandbox.interswitchng.com/passport/oauth/token

The configuration separates environment-specific values from Java code.

For example:

interswitch.test.baseUrl=...

interswitch.live.baseUrl=...

allows the application to switch between test and production environments without changing the service implementation.

Do not commit secrets

Although application.properties is convenient during development, production applications should not store credentials directly in source control.

A better approach is:

interswitch.client.id=${INTERSWITCH_CLIENT_ID}

interswitch.client.secret=${INTERSWITCH_CLIENT_SECRET}

The actual values can then be supplied through environment variables.

For example:

export INTERSWITCH_CLIENT_ID=your-client-id

export INTERSWITCH_CLIENT_SECRET=your-client-secret

This is especially important if the project will eventually be pushed to GitHub or deployed to a cloud server.

Step 4: Create the RestTemplate Configuration

Spring Boot needs an HTTP client to communicate with the Interswitch API.

For this implementation, we use Spring's RestTemplate.

Create:

vas/config/RestTemplateConfig.java

package com.installment.core.vas.config;

 import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.client.RestTemplate;

 @Configuration

public class RestTemplateConfig {

     @Bean

    public RestTemplate restTemplate() {

        return new RestTemplate();

    }

}

The @Configuration annotation tells Spring that this class contains application configuration.

The @Bean annotation registers the RestTemplate with Spring's application context.

We can then inject it into our service using constructor injection.

Step 5: Create the Interswitch Token Response DTO

The authentication server returns JSON containing the access token and other information.

Instead of working with raw JSON, we create a Java DTO.

Create:

vas/dto/InterswitchTokenResponse.java

package com.installment.core.vas.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor

 public class InterswitchTokenResponse {

    @JsonProperty("access_token")

    private String accessToken;

     @JsonProperty("token_type")

    private String tokenType;

    @JsonProperty("expires_in")

    private Long expiresIn;

    private String scope;

    @JsonProperty("merchant_code")

    private String merchantCode;

    @JsonProperty("merchant-wallet-actions")

    private List<String> merchantWalletActions;

    @JsonProperty("terminalId")

    private String terminalId;

    private String env;

    @JsonProperty("client_name")

    private String clientName;

     private String email;

}

The @JsonProperty annotation is important because the naming convention used by the external API may not match Java's normal camel-case convention.

For example, the external response may contain:

{

    "access_token": "eyJ...",

    "token_type": "Bearer",

    "expires_in": 3600

}

while our Java property is:

private String accessToken;

Jackson uses @JsonProperty("access_token") to connect the two.

Step 6: Create the Interswitch Authentication Service

Now we implement the most important part of the integration.

Create:

vas/auth/InterswitchAuthService.java

package com.installment.core.vas.auth;

 import com.installment.core.vas.dto.InterswitchTokenResponse;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.http.*;

import org.springframework.stereotype.Service;

import org.springframework.util.LinkedMultiValueMap;

import org.springframework.util.MultiValueMap;

import org.springframework.web.client.RestTemplate;

 

import java.nio.charset.StandardCharsets;

import java.util.Base64;

 @Service

public class InterswitchAuthService {

     private final RestTemplate restTemplate;

     @Value("${interswitch.auth.url}")

    private String authUrl;

     @Value("${interswitch.client.id}")

    private String clientId;

 

    @Value("${interswitch.client.secret}")

    private String clientSecret;

 

    public InterswitchAuthService(RestTemplate restTemplate) {

        this.restTemplate = restTemplate;

    }

 

    public InterswitchTokenResponse authenticate() {

 

        String credentials = clientId + ":" + clientSecret;

 

        String encodedCredentials =

                Base64.getEncoder()

                        .encodeToString(

                                credentials.getBytes(StandardCharsets.UTF_8)

                        );

 

        HttpHeaders headers = new HttpHeaders();

 

        headers.set(

                HttpHeaders.AUTHORIZATION,

                "Basic " + encodedCredentials

        );

 

        headers.setContentType(

                MediaType.APPLICATION_FORM_URLENCODED

        );

 

        MultiValueMap<String, String> body =

                new LinkedMultiValueMap<>();

 

        body.add("scope", "profile");

        body.add("grant_type", "client_credentials");

 

        HttpEntity<MultiValueMap<String, String>> request =

                new HttpEntity<>(body, headers);

 

        ResponseEntity<InterswitchTokenResponse> response =

                restTemplate.exchange(

                        authUrl,

                        HttpMethod.POST,

                        request,

                        InterswitchTokenResponse.class

                );

 

        if (response.getBody() == null ||

                response.getBody().getAccessToken() == null) {

 

            throw new RuntimeException(

                    "Unable to obtain Interswitch access token"

            );

        }

 

        return response.getBody();

    }

}

Let's break down what is happening.

Step 6.1: Inject the Configuration

The service obtains the authentication URL and credentials from application.properties:

@Value("${interswitch.auth.url}")

private String authUrl;

 

@Value("${interswitch.client.id}")

private String clientId;

 

@Value("${interswitch.client.secret}")

private String clientSecret;

This prevents the credentials from being hard-coded into the service.

Step 6.2: Build the Basic Authentication Credentials

OAuth 2.0 client credentials are combined in this format:

clientId:clientSecret

The code creates that string:

String credentials = clientId + ":" + clientSecret;

It is then Base64 encoded:

String encodedCredentials =

        Base64.getEncoder()

                .encodeToString(

                        credentials.getBytes(StandardCharsets.UTF_8)

                );

The resulting value is placed in the HTTP Authorization header:

Authorization: Basic <base64-credentials>

This is a common pattern for OAuth 2.0 client authentication.

Step 6.3: Set the Content Type

The authentication request uses form-encoded data rather than JSON.

Therefore, we specify:

headers.setContentType(

        MediaType.APPLICATION_FORM_URLENCODED

);

The request body will look conceptually like:

scope=profile

grant_type=client_credentials

Step 6.4: Create the Request Body

Spring's MultiValueMap makes it easy to construct form data:

MultiValueMap<String, String> body =

        new LinkedMultiValueMap<>();

 

body.add("scope", "profile");

body.add("grant_type", "client_credentials");

The important OAuth parameter is:

grant_type=client_credentials

This tells the authentication server that the application is authenticating using its registered client credentials.

Step 6.5: Create the HTTP Request

We combine the headers and body:

HttpEntity<MultiValueMap<String, String>> request =

        new HttpEntity<>(body, headers);

The request now contains everything required by the authentication endpoint.

Step 6.6: Send the Authentication Request

We use RestTemplate.exchange():

ResponseEntity<InterswitchTokenResponse> response =

        restTemplate.exchange(

                authUrl,

                HttpMethod.POST,

                request,

                InterswitchTokenResponse.class

        );

The parameters tell Spring:

  1. Where to send the request.
  2. Which HTTP method to use.
  3. What headers and body to send.
  4. Which Java class should represent the response.

The response JSON is automatically deserialized into:

InterswitchTokenResponse

Step 6.7: Validate the Response

It is important not to assume that every request will return a valid token.

We therefore check:

if (response.getBody() == null ||

        response.getBody().getAccessToken() == null) {

 

    throw new RuntimeException(

            "Unable to obtain Interswitch access token"

    );

}

If an access token exists, the DTO is returned:

return response.getBody();

At this point, our Spring Boot application can successfully communicate with the Interswitch authentication server.

Step 7: Create a Standard API Response Wrapper

In a real application, it is often useful to return API responses using a consistent structure.

For this project, we use:

package com.installment.core.utils;

 

import lombok.AllArgsConstructor;

import lombok.Getter;

import lombok.NoArgsConstructor;

import lombok.Setter;

 

@Getter

@Setter

@NoArgsConstructor

@AllArgsConstructor

public class AppApiResponse<T> {

     private boolean success;

     private String message;

     private T data;

}

The generic <T> allows the wrapper to work with different response types.

For example:

AppApiResponse<InterswitchTokenResponse>

means the data field contains an InterswitchTokenResponse.

The API response can therefore have a consistent structure:

{

    "success": true,

    "message": "Interswitch authentication successful",

    "data": {

        "access_token": "...",

        "token_type": "Bearer",

        "expires_in": 3600

    }

}

Step 8: Create the Authentication Controller

Now we expose our authentication service through a REST endpoint.

Create:

vas/controller/InterswitchAuthController.java

A simplified version is:

package com.installment.core.vas.controller;

 import com.installment.core.utils.AppApiResponse;

import com.installment.core.vas.auth.InterswitchAuthService;

import com.installment.core.vas.dto.InterswitchTokenResponse;

 import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.*;

 

@RestController

@RequestMapping("/api/v1/vas/interswitch")

public class InterswitchAuthController {

 

    private final InterswitchAuthService authService;

 

    public InterswitchAuthController(

            InterswitchAuthService authService) {

 

        this.authService = authService;

    }

 

    @PostMapping("/auth")

    public ResponseEntity<

            AppApiResponse<InterswitchTokenResponse>

            > authenticate() {

         InterswitchTokenResponse response =

                authService.authenticate();

         AppApiResponse<InterswitchTokenResponse> apiResponse =

                new AppApiResponse<>(

                        true,

                        "Interswitch authentication successful",

                        response

                );

 

        return ResponseEntity.ok(apiResponse);

    }

}

The endpoint is now:

POST /api/v1/vas/interswitch/auth

The controller has a very simple responsibility:

The controller does not know how authentication works internally. That logic remains inside InterswitchAuthService.

Step 9: Add OpenAPI Documentation

If your Spring Boot application uses Springdoc OpenAPI, you can document the endpoint directly in the controller.

For example:

@Operation(

    summary = "Generate Interswitch access token",

    description =

        "Authenticates with the Interswitch sandbox using " +

        "OAuth 2.0 client credentials and returns an access token."

)

@ApiResponses(value = {

     @ApiResponse(

        responseCode = "200",

        description = "Authentication successful"

    ),

 

    @ApiResponse(

        responseCode = "401",

        description = "Invalid client credentials"

    ),

 

    @ApiResponse(

        responseCode = "400",

        description = "Invalid authentication request"

    ),

 

    @ApiResponse(

        responseCode = "500",

        description = "Internal server error"

    )

})

This makes the endpoint easier to test from Swagger UI and also provides documentation for other developers working with the integration.

One small correction worth making in the original implementation is the controller tag.

Instead of:

@Tag(

    name = "User Authentication",

    description = "APIs for merchant login"

)

use something specific to the integration:

@Tag(

    name = "Interswitch VAS",

    description = "Interswitch VAS integration APIs"

)

The endpoint is authenticating the application with Interswitch

Step 10: Build the Application

Before testing the integration, compile the project.

With Maven:

mvn clean package

If the build succeeds, Maven should produce a JAR under:

target/

For example:

target/installment-core-0.0.1-SNAPSHOT.jar

If you want to skip tests temporarily:

mvn clean package -DskipTests

However, during normal development it is better to keep the tests enabled.

Step 11: Run the Application

The application can be started using Maven:

mvn spring-boot:run

Or by running the generated JAR:

java -jar target/installment-core-0.0.1-SNAPSHOT.jar

When Spring Boot starts successfully, you should see a message similar to:

Started Application in ... seconds

The application is now ready to receive requests.

Step 12: Test the Authentication Endpoint

We can test the endpoint using Postman, Insomnia, curl or Swagger UI.

Using curl:

curl --location --request POST \

  http://localhost:8080/api/v1/vas/interswitch/auth

No Interswitch client credentials need to be supplied by the caller because they are already configured on the server.

If authentication succeeds, the application returns the token response.

A response could look conceptually like:

{

    "success": true,

    "message": "Interswitch authentication successful",

    "data": {

        "access_token": "eyJ...",

        "token_type": "Bearer",

        "expires_in": 3600,

        "scope": "profile",

        "merchant_code": "YOUR_MERCHANT_CODE"

    }

}

The exact response fields depend on the response returned by the Interswitch environment being used.

The access token can subsequently be supplied in the Authorization header when making authenticated VAS requests.

Conceptually:

Authorization: Bearer <access-token>

This gives the application a reusable authentication layer rather than implementing OAuth authentication separately for every VAS operation.

Improving the Production Implementation

The implementation above is intentionally straightforward so that the authentication flow is easy to understand.

However, there are several improvements that should be considered before deploying it to production.

1. Do not authenticate for every request

The current implementation obtains a new access token whenever:

POST /api/v1/vas/interswitch/auth

is called.

A production integration should normally consider token caching.

If the token is valid for a particular period, the application can store it temporarily and reuse it until it is close to expiration.

For example:

This reduces unnecessary authentication requests.

2. Move secrets to environment variables

Instead of:

interswitch.client.secret=actual-secret

use:

interswitch.client.secret=${INTERSWITCH_CLIENT_SECRET}

This prevents credentials from being accidentally committed to Git.

3. Use proper exception handling

The example uses:

throw new RuntimeException(

    "Unable to obtain Interswitch access token"

);

A production application should ideally introduce a custom exception and centralized exception handling.

For example:

InterswitchAuthenticationException

can be handled by a global exception handler and converted into a consistent API response.

4. Add timeout configuration

External API calls should not be allowed to wait indefinitely.

The HTTP client should have appropriate:

  • Connection timeout
  • Read timeout
  • Connection pool configuration

This becomes particularly important for payment and VAS systems.

5. Add logging without exposing credentials

Logging is useful when troubleshooting integration problems.

However, never log:

client_secret

Authorization header

access_token

A log message such as:

Interswitch authentication request initiated

is preferable to logging the complete request.

Conclusion

Integrating a VAS provider into a Spring Boot application starts with establishing a reliable authentication layer.

In this tutorial, we built that layer by:

  1. Creating a Spring Boot application.
  2. Structuring the VAS integration into separate packages.
  3. Configuring Interswitch properties.
  4. Creating a reusable RestTemplate.
  5. Creating a DTO for the token response.
  6. Implementing OAuth 2.0 client-credentials authentication.
  7. Encoding the client credentials using Base64.
  8. Sending a form-encoded authentication request.
  9. Mapping the authentication response to a Java object.
  10. Creating a REST controller.
  11. Wrapping the response in a standard API response.
  12. Building and running the application.
  13. Testing the authentication endpoint.
  14. Preparing the architecture for subsequent VAS operations.

The authentication endpoint is only the first building block. Once it is working, the same architecture can be extended to product discovery, airtime and data purchases, transaction verification, wallet operations and other VAS capabilities.