DEVELOPER

Back to Developer Blog

technicalseries

Programmatic Merchant Onboarding in Python

By Ndafara Tsamba and Laura Olson | October 16th, 2024

Merchant onboarding is the process of acquiring and verifying merchants for payment processing services, such as credit and debit card payments and other methods like Google Pay and Apple Pay. The process involves several steps to ensure ISVs and payment service providers who facilitate the boarding process comply with strict regulations, which can be cumbersome and time-consuming.

Merchant onboarding typically involves gathering information from the merchant, verifying their identity, assessing their risk profile, and setting up their account with a payment service provider. Once the merchant has been approved, the payment company provides them with the necessary tools and resources to start accepting payments, such as a payment terminal or software. However, ISVs can simplify and shorten this process by automating merchant onboarding. In this article, you'll learn how to onboard merchants using North's integration for Simplified Enrollment in Python.

Click through the interactive visualizer below to view details about the API calls that are used in the Simplified Enrollment flow.

Build this App

Clone this code repository to quickly create your own app today!

Get Authentication Token
Create New Application
Send Link to Merchant
Validate Application
Submit Application
Step 5
Submit Application
enroll/application/submit/{key}

Submit a completed application to Underwriting for review.

Prerequisites

Set up the following to complete this tutorial:

  • A Python installation. You will be using Python version 3.11 or later. Run the command python --version to see the Python version number that you currently have.
  • A virtual environment using env. You will need a virtual environment to create an isolated environment that contains all the necessary dependencies and libraries required for your demo application.

To create the user interface for your application, you will also need the following:

  • Node.JS version 16 and above. Run node -v 1 to check your version.
  • Node Package Manager (NPM) version 9.8+. Run npm -v to check which version you have.

You can find the complete source code for this tutorial on GitHub .

How To Onboard Merchants With Python

Set Up a Python Project

Create a new directory named Payments_Hub_Merchant_Onboarding_Demo. It will contain all your Python code.

Navigate to the Payments_Hub_Merchant_Onboarding_Demo directory and run the following command to create a virtual environment called payments_hub_merchant_onboarding_demo:

python -m venv payments_hub_merchant_onboarding_demo

After creating the virtual environment, you need to activate it. On Windows, run the following command:

payments_hub_merchant_onboarding_demo\Scripts\activate

To activate the virtual environment on Linux or Mac, run this command:

source payments_hub_merchant_onboarding_demo/bin/activate

Once the virtual environment is activated, install the requests package by running the following command:

pip install requests

Get in Touch

Talk to us about automating onboarding for Merchant Processing Accounts.

Authentication

Before accessing the Merchant Boarding API, you must authenticate by calling the auth endpoint. Make a POST request and provide the following keys and values in the x-www-form-urlencoded format:

  • grant_type: client_credentials
  • scope: all
  • client_id: your_client_id
  • client_secret: your_client_secret

If your request is successful, you will receive a 200 OK success code and a bearer token that is valid for five minutes. You will need to include this bearer token in the headers of subsequent requests to the Merchant Boarding API in the format Bearer token.

In the payments_hub_merchant_onboarding_demo directory, create a file named get_oauth_token.py and add the following code to it:

import requests
import pprint

# Set the request parameters
url = 'https://enrollment-api-auth.paymentshub.com'
path = "/oauth/token"

# Set proper headers
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

# Set the URL-encoded body
grant_type = 'client_credentials'
scope = 'all'
client_id = 'your_client_id'
client_secret = 'your_client_secret'

data = {'grant_type': grant_type, 'scope': scope, 'client_id': client_id, 'client_secret': client_secret}

# HTTP request
response = requests.post(url + path, headers=headers, data=data)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Get the JSON response
data = response.json()
pprint.pprint(data, indent=2)

Remember to replace your_client_id and your_client_secret with your actual values.

The code above calls the authentication endpoint, and if successful, it prints out an access token that you can use for subsequent calls to the Merchant Boarding API.

To execute the file, run the following command on the terminal from the root directory:

python get_oauth_token.py

The successful output will look as follows:

{ 'access_token': 'access_token_value',
  'expires_in': 300,
  'scope': 'create',
  'token_type': 'bearer'}

The output is a JSON object with the following keys: access_token, expires_in, token_type, and scope. You will need to use the access_token on further API calls, but bear in mind that it expires after three hundred seconds, or five minutes.

Create a New North Application

After authenticating, call the Merchant Boarding API and create a new application. Before starting this new application, either create an application template or note the plan ID of an existing template.

For this tutorial, you will make a call to the Sandbox endpoint /enroll/application and provide the following mandatory fields: agent, applicationName, externalKey, and plan. The following fields are optional: shipping, principals, business, bankAccount, and epxHierarchy.

  • agent: the agent or sales rep ID
  • applicationName: string that can be used to identify the application
  • externalKey: unique identifier for application
  • plan: equipment plan object
  • shipping: shipping information model
  • principals: business principal object
  • business: business object
  • bankAccount: bank account object
  • epxHierarchy: enrollment EPX customer hierarchy object

In the payments_hub_merchant_onboarding_demo directory, create a file called create_application.py and add the following code to it:

import requests
import json
import pprint

# Set the request parameters
url = 'https://enrollment-api-sandbox.paymentshub.com'
path = '/enroll/application'

bearer_token = 'PUT_YOUR_ACCESS_TOKEN_HERE'

# Set proper headers
headers = {'Content-Type': 'application/json',
           'Authorization': 'Bearer ' + bearer_token}

# Set the mandatory urlencoded body
payload = {
    "agent": "PUT_YOUR_AGENT_ID_HERE",
    "applicationName": "Test Application 1",
    "externalKey": "012345-ABC",
    "plan": {
        "planId": "PUT_YOUR_PLAN_ID_HERE"
    },
    "principals": [
        {
            "firstName": "Jane",
            "lastName": "Jackson",
            "socialSecurityNumber": "12345678",
            "dateOfBirth": "1955-12-25",
            "phoneNumber": "1234567890",
            "email": "user@example.com",
            "street": "123 Selah Way",
            "street2": "Suite 123",
            "zipCode": "12345",
            "city": "South Burlington",
            "state": "VT",
            "equityOwnershipPercentage": 50,
            "title": "ceo",
            "isPersonalGuarantor": True,
            "driverLicenseNumber": "ABC1234567890",
            "driverLicenseIssuedState": "MI"
        },
        {
            "firstName": "Jeremy",
            "lastName": "Coelman",
            "socialSecurityNumber": "12345678",
            "dateOfBirth": "1977-12-25",
            "phoneNumber": "1234567890",
            "email": "user@example.com",
            "street": "1234 Finwood Drive",
            "zipCode": "12345",
            "city": "Red Bank",
            "state": "NJ",
            "equityOwnershipPercentage": 50,
            "title": "manager",
            "isPersonalGuarantor": False,
            "driverLicenseNumber": None,
            "driverLicenseIssuedState": None
        }
    ],
    "business": {
        "corporateName": "Joe's Spaceage Stereo",
        "dbaName": "Joe's Stereo",
        "businessType": "C",
        "federalTaxIdNumber": "123567890",
        "federalTaxIdType": "EIN",
        "mcc": "1234",
        "phone": "1234567890",
        "email": "user@example.com",
        "websites": [
            {
                "url": "https://example.com",
                "websiteCustomerServiceEmail": "customer-service-email@example.com",
                "websiteCustomerServicePhoneNumber": "1234567890"
            }
        ],
        "averageTicketAmount": 5000,
        "averageMonthlyVolume": 1250000,
        "highTicketAmount": 125000,
        "merchandiseServicesSold": "Audio components and services",
        "percentOfBusinessTransactions": {
            "cardSwiped": 65,
            "keyedCardPresentNotImprinted": 20,
            "mailOrPhoneOrder": 0,
            "internet": 15
        },
        "businessContact": {
            "firstName": "Roy",
            "lastName": "Martin",
            "socialSecurityNumber": "123456789",
            "dateOfBirth": "1947-11-05",
            "street": "123 Late Avenue",
            "street2": "",
            "zipCode": "12345",
            "city": "South Burlington",
            "state": "VT",
            "phoneNumber": "1234567890",
            "email": "user@example.com"
        },
        "statementDeliveryMethod": "electronic",
        "businessAddress": {
            "dba": {
                "street": "1234 Clinton St",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            },
            "corporate": {
                "street": "1234 Sun Valley Rd",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            },
            "shipTo": {
                "street": "1234 Saint James Drive",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            }
        }
    },
    "bankAccount": {
        "abaRouting": "12345678",
        "accountType": "checking",
        "demandDepositAccount": "12345678"
    }
}

# Convert the payload to JSON String
json_payload = json.dumps(payload)

# Do the HTTP request
response = requests.post(url + path, headers=headers, data=json_payload)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Get the JSON response
data = response.json()
pprint.pprint(data, indent=2)

This code will create a new application.

Remember to replace PUT_YOUR_ACCESS_TOKEN_HERE with the valid bearer token that you received after running get_oauth.py. Also, replace PUT_YOUR_AGENT_ID_HERE and PUT_YOUR_PLAN_ID_HERE with your credentials. You can keep the values provided for principals, business, and bank_account_details or replace the values with the details you want to test with.

To execute the file, run the following command on the terminal from the root directory:

python create_application.py

A successful request will return a 201 status code and details of the application you created. The status is success, which signifies that your application was successfully created.

If your request is not valid, you will receive a 400 Bad Request response, and if you use an invalid or expired token, you will receive a 401 Invalid authorization response.

Send the Application to the Merchant

After creating the application, the next step is to send it to the merchant for completion. In production, an email with a link to the application will be sent to the merchant. The endpoint used is /enroll/application/merchant/send/key/{externalKey}, and the method is PUT. The externalKey should match the externalKey used when creating the application; in this case, it was 012345-ABC.

In the payments_hub_merchant_onboarding_demo directory, create a file called send_application.py and add the following code to it:

import requests
import pprint

# Set your access token
bearer_token = 'PUT_YOUR_ACCESS_TOKEN_HERE'

# Set the external key
externalKey = '012345-ABC'

# Set the request parameters
url = 'https://enrollment-api-sandbox.paymentshub.com'
path = '/enroll/application/merchant/send/key/' + externalKey

# Set proper headers
headers = {'Content-Type': 'application/x-www-form-urlencoded',
           'Authorization': 'Bearer ' + bearer_token}

# Do the HTTP request
response = requests.put(url + path, headers=headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Get the JSON response
data = response.json()
pprint.pprint(data, indent=2)

Run the script using the following command:

python send_application.py

The successful output will look as follows:

{ 'data': { 'applicationStatus': 'waiting for merchant',
            'merchantId': None,
            'underwritingStatus': 'Pending Submission'},
  'link': '/application/merchant/send/key/012345-ABC',
  'status': 'success'}

Validate the Application

After you send the application, the merchant will complete it. Before the completed application can be sent for underwriting review, it must be validated to check that the data the merchant entered meets the parameter requirements.

The endpoint to use here is /enroll/application/validate/{externalKey}. This endpoint validates that the data entered in the application meets the parameter requirements before the application is submitted for underwriting.

In the payments_hub_merchant_onboarding_demo directory, create a file called validate_application.py and put the following code in it:

import requests
import pprint


# Set the external key
externalKey = 'externalKey'

# Set the request parameters
url = 'https://enrollment-api-sandbox.paymentshub.com/enroll/application/validate/' + externalKey

# Set proper headers
headers = {'Content-Type': 'application/x-www-form-urlencoded',
           'Authorization': 'Bearer PUT_YOUR_TOKEN_HERE'}

# Do the HTTP request
response = requests.get(url, headers=headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    print('Status:', response.status_code, 'Headers:', response.headers, 'Error Response:', response.json())
    exit()

# Get the JSON response
data = response.json()
pprint.pprint(data, indent=2)

Run the following command to execute the script:

python validate_application.py

The successful output will look as follows:

{ 'data': {'errors': [], 'readyToSubmit': True},
  'link': '/application/validate/012345-ABC',
  'status': 'success'}

A success status means that there are no errors and that the application can now be submitted to underwriting.

Submit the Application

If your application validation is successful, the last step is to submit the application to underwriting. The endpoint to submit the application is /enroll/application/submit/{externalKey}.

In the payments_hub_merchant_onboarding_demo directory, create a file called submit_application.py and add the following code to it:

import requests
import pprint

# Set the external key
externalKey = 'externalKey'

# Set the request parameters
path = '/enroll/application/submit/' + externalKey
 
url = 'https://enrollment-api-sandbox.paymentshub.com' + path

# Set proper headers
headers = {'Content-Type': 'application/x-www-form-urlencoded',
           'Authorization': 'Bearer PUT_YOUR_TOKEN_HERE'}

# Do the HTTP request
response = requests.put(url, headers=headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    print('Status:', response.status_code, 'Headers:', response.headers, 'Error Response:', response.json())
    exit()

# Decode the JSON response into a dictionary and use the data
data = response.json()
print(data)

Remember to replace PUT_YOUR_TOKEN_HERE with your actual token.

To execute the script, run the following command:

python submit_application.py

The successful output will look as follows:

{ 'data': { 'applicationStatus': 'finalized',
            'merchantId': '3130034240103',
            'underwritingStatus': 'Enrollment'},
  'link': '/application/submit/012345-ABC',
  'status': 'success'}

The applicationStatus value of finalized signifies that the application was successfully submitted.

Demonstrating the Complete Flow

Now that you know how the individual parts of the simplified enrollment process work, let's see how the complete flow works.

In the payments_hub_merchant_onboarding_demo directory, create a file called demonstrate_application.py and add the following code to it:

import requests
import pprint
import json

# Set the External Key
external_key = 'DemoApp-123'

# Set the URLs
base_url = 'https://enrollment-api-sandbox.paymentshub.com'
token_base_url = 'https://enrollment-api-auth.paymentshub.com'
token_url = f'{token_base_url}/oauth/token'

create_application_url = f'{base_url}/enroll/application'
send_application_url = f'{base_url}/enroll/application/merchant/send/key/{external_key}'
validate_application_url = f'{base_url}/enroll/application/validate/{external_key}'
submit_application_url = f'{base_url}/enroll/application/submit/{external_key}'

# Set proper Oauth Headers
headers = {'Content-Type': 'application/x-www-form-urlencoded'}

# Set the urlencoded body
grant_type = 'client_credentials'
scope = 'all'
client_id = 'PUT_YOUR_CLIENT_ID_HERE'
client_secret = 'PUT_YOUR_CLIENT_SECRET_HERE'

data = {'scope': scope, 'grant_type': grant_type,
        'client_id': client_id, 'client_secret': client_secret}

# Do the HTTP request
response = requests.post(token_url, headers=headers, data=data)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint('Could not get Token!')
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Get the JSON response
data = response.json()

# Save token
bearer_token = data['access_token']

# Create the Application
# Set proper Send Application Headers
headers = {'Content-Type': 'application/json',
           'Authorization': 'Bearer ' + bearer_token}

# Set the mandatory urlencoded body
application_payload = {
    "agent": PUT_YOUR_AGENT_ID_HERE,
    "applicationName": "Demo Application 1",
    "externalKey": "DemoApp-123",
    "plan": {
        "planId": PUT_YOUR_PLAN_ID_HERE
    },
    "principals": [
        {
            "firstName": "Jane",
            "lastName": "Jackson",
            "socialSecurityNumber": "123456789",
            "dateOfBirth": "1955-12-25",
            "phoneNumber": "1234567890",
            "email": "user@example.com",
            "street": "123 Selah Way",
            "street2": "Suite 123",
            "zipCode": "12345",
            "city": "South Burlington",
            "state": "VT",
            "equityOwnershipPercentage": 50,
            "title": "ceo",
            "isPersonalGuarantor": True,
            "driverLicenseNumber": "ABC1234567890",
            "driverLicenseIssuedState": "MI"
        },
        {
            "firstName": "Jeremy",
            "lastName": "Coelman",
            "socialSecurityNumber": "123456789",
            "dateOfBirth": "1977-12-25",
            "phoneNumber": "1234567890",
            "email": "user@example.com",
            "street": "1234 Finwood Drive",
            "zipCode": "12345",
            "city": "Red Bank",
            "state": "NJ",
            "equityOwnershipPercentage": 50,
            "title": "manager",
            "isPersonalGuarantor": False,
            "driverLicenseNumber": None,
            "driverLicenseIssuedState": None
        }
    ],
    "business": {
        "corporateName": "Joe's Spaceage Stereo",
        "dbaName": "Joe's Stereo",
        "businessType": "C",
        "federalTaxIdNumber": "123567890",
        "federalTaxIdType": "EIN",
        "mcc": "1234",
        "phone": "1234567890",
        "email": "user@example.com",
        "websites": [
            {
                "url": "https://example.com",
                "websiteCustomerServiceEmail": "customer-service-email@example.com",
                "websiteCustomerServicePhoneNumber": "1234567890"
            }
        ],
        "averageTicketAmount": 5000,
        "averageMonthlyVolume": 1250000,
        "highTicketAmount": 125000,
        "merchandiseServicesSold": "Audio components and services",
        "percentOfBusinessTransactions": {
            "cardSwiped": 65,
            "keyedCardPresentNotImprinted": 20,
            "mailOrPhoneOrder": 0,
            "internet": 15
        },
        "businessContact": {
            "firstName": "Roy",
            "lastName": "Martin",
            "socialSecurityNumber": "123456789",
            "dateOfBirth": "1947-11-05",
            "street": "123 Late Avenue",
            "street2": "",
            "zipCode": "12345",
            "city": "South Burlington",
            "state": "VT",
            "phoneNumber": "1234567890",
            "email": "user@example.com"
        },
        "statementDeliveryMethod": "electronic",
        "businessAddress": {
            "dba": {
                "street": "1234 Clinton St",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            },
            "corporate": {
                "street": "1234 Sun Valley Rd",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            },
            "shipTo": {
                "street": "1234 Saint James Drive",
                "city": "South Burlington",
                "state": "VT",
                "zipCode": "12345"
            }
        }
    },
    "bankAccount": {
        "abaRouting": "123456789",
        "accountType": "checking",
        "demandDepositAccount": "123456789"
    }
}

# Convert the payload to JSON String
json_payload = json.dumps(application_payload)

# Do the HTTP request
response = requests.post(create_application_url, headers=headers, data=json_payload)

# Check for HTTP codes other than 200
if response.status_code != 201:
    pprint.pprint('Could not get create Application!')
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Send the application
# Set proper Send Application Headers
send_submit_headers = {'Content-Type': 'application/x-www-form-urlencoded',
                       'Authorization': 'Bearer ' + bearer_token}

# Do the HTTP request
response = requests.put(send_application_url, headers=send_submit_headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint('Could not send the application!')
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Validate Application
# Set proper Validation Headers
validate_headers = {'Authorization': 'Bearer ' + bearer_token}

response = requests.get(validate_application_url, headers=validate_headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint('Could not validate the application!')
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Submit Application
# Set the request parameters

# Set proper Submit Headers
headers = {'Content-Type': 'application/x-www-form-urlencoded',
           'Authorization': 'Bearer ' + bearer_token}

# Do the HTTP request
response = requests.put(submit_application_url, headers=send_submit_headers)

# Check for HTTP codes other than 200
if response.status_code != 200:
    pprint.pprint('Could not get Submit the application!')
    pprint.pprint(f'Status: {response.status_code} Headers: {response.headers} Error Response: {response.json()}')
    exit()

# Decode the JSON response into a dictionary and use the data
data = response.json()
pprint.pprint(data, indent=2)

The code above combines the different functions from this tutorial into a single application for onboarding a merchant.

Be sure to replace PUT_YOUR_CLIENT_ID_HERE, PUT_YOUR_CLIENT_SECRET_HERE, PUT_YOUR_AGENT_ID_HERE, and PUT_YOUR_PLAN_ID_HERE with your credential values.

To run the full application, run the following command:

python demonstrate_application.py

If it is successful, you will receive a success response that an application with the external key DemoApp-123 was created:

{ 'data': { 'applicationStatus': 'finalized',
            'merchantId': '3130034240111',
            'underwritingStatus': 'Enrollment'},
  'link': '/application/submit/DemoApp-123',
  'status': 'success'}

If you used a different external key, the message will contain the external key you used.

Web Application

The next section will explain how to build a user interface for your merchant onboarding application.
Merchant onboarding application made with North's Merchant Boarding API

Set Up the User Interface

The code for the web application user interface is in four main files, index.html, packages.json, proxy.js and server.js.

The HTML in the index.html file creates a form with the fields required to create a merchant application. It also has JavaScript code enclosed between the script tags that fetches a token and then uses it to create a merchant application.

The form comes pre-populated with test values that you can replace before you submit your application. Be sure to complete the agent, external key, and plan id fields before you submit as you can't submit the application if they are not completed.

The form also has two buttons at the bottom: Generate Token and Submit. The Generate Token button is first used to get the authentication token after which the Submit button submits the application.

The packages.json file contains metadata about the application and also lists the dependencies required.

The proxy.js file is necessary to avoid cross-origin resource sharing (CORS) issues . The proxy is responsible for sending API calls to the North API.

Remember to replace your_client_id and your_client_secret with your credentials.

And, lastly, the server.js file serves the index.html page for the user interface.

Run the Application

To run the application, run the following command on the terminal to install the dependencies: npm install

Next, run the command node proxy.js to start the proxy. You should get the following output:

Server running at http://localhost:3001

Open another terminal and run the command node server.js to start the server script that's responsible for serving the user interface, ie the index.html page. You should get the following output:

Server is running on http://localhost:3000

On the form, click on Generate Token to get an auth token. Wait for the modal to confirm that you now have a token.

You will also see the token displayed on the terminal that is running proxy.js:

Response: {
access_token: 'access_token_value',
expires_in: 300,
token_type: 'bearer',
scope: 'create'
}

Click on Submit to submit the form. The auth token will be automatically added to the request in proxy.js, a payload will be created using the details in the form, and it will be sent to the /enroll/application/submit endpoint.

You will get a confirmation that the merchant application was successfully created.

Conclusion

This step-by-step guide has shown you how simple and efficient it is to onboard a merchant using Python and North. You can use these steps to streamline your onboarding process and reduce the risk of fraud or application data errors.

How To Get Started

North’s Sales Engineering team provides support to developers and business decision-makers to help select the best possible payment system. Contact us to learn more about how North can help you set up a secure, efficient, and easy-to-use merchant boarding flow.


Start your free Developer account and try it now.


©2025 North is a registered DBA of NorthAB, LLC. All rights reserved. North is a registered ISO of BMO Harris Bank N.A., Chicago, IL, Citizens Bank N.A., Providence, RI, The Bancorp Bank, Philadelphia, PA, FFB Bank, Fresno, CA, Wells Fargo Bank, N.A., Concord, CA, and PNC Bank, N.A.