Merchant Account at RelayPay

This is a guide for merchants integrating with RelayPay as a payment processor.

Before starting you need to visit the Merchant Dashboard found at https://app.sandbox.relaypay.io and create an account.

This is the admin dashboard you can use to see transactions and update any settings associated with your account.

When you complete you profile we’ll need to activate your account. Please reach out to us to complete KYB.

Once know-you-business verification is complete you will be able to generate private and public keys in the UI to be used for API requests.

Sandbox environment

The API Base url for the sandbox environment is api.sandbox.relaypay.io

Sandbox environment is intended for development of an integration and testing purposes. Transactions on sandbox do not require real money nor crypto currency to test. See testing.

Production environment

API base url is for the production environment is api.relaypay.io

In order to use the production environment, as mentioned earlier, you’ll need to open an account at https://app.relaypay.io

Overview of Flow

Authentication

All our rest endpoints are secure and any caller must provide authentication to the request. Al requests must be standard HTTPS protocol. plain HTTP is not supported.

Common headers

  • the request must have a header name x-api-key with public api key as value.
  • the request must have a header name x-merchant-id with your merchantId (obtained when creating the account).

POST and PUT requests

  • the request body must be application/json
  • the request must have a header name x-api-signaturewith calculated value based on the json payload.

x-api-signature header

Authenticated requests should be signed with x-api-signature header, using a signature generated with SHA 256 of stringified JSON payload and private key according to:

SHA256(payload + privateKey)

Example Sign

Below is a specific example of a sign generated with a particular stringified JSON payload and private key. The request payload used for generating sign and sending API request should be identical to avoid any issue because same payload with different whitespace can generate entirely different signature. Code snippets for generating the sign in Node.js, Python and Java are shown below:

FieldValue
Private (Secret) KeyDOSsQcRP9NnzFJxVQL4W
Payload{"orderId":"12345","transactionId":"advgjq-1ba7ak-asdhja8","orderStatus":"Success"}
Concat the json String and secret key String{"orderId":"12345","transactionId":"advgjq-1ba7ak-asdhja8","orderStatus":"Success"}DOSsQcRP9NnzFJxVQL4W
The UTF-8 String converted to byte array[123, 34, 111, 114, 100, 101, 114, 73, 100, 34, 58, 34, 49, 50, 51, 52, 53, 34, 44, 34, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 73, 100, 34, 58, 34, 97, 100, 118, 103, 106, 113, 45, 49, 98, 97, 55, 97, 107, 45, 97, 115, 100, 104, 106, 97, 56, 34, 44, 34, 111, 114, 100, 101, 114, 83, 116, 97, 116, 117, 115, 34, 58, 34, 83, 117, 99, 99, 101, 115, 115, 34, 125, 68, 79, 83, 115, 81, 99, 82, 80, 57, 78, 110, 122, 70, 74, 120, 86, 81, 76, 52, 87]
Calculate the SHA-256 digest from the byte array and return the value as a byte array.[106, -64, 126, -38, -12, -28, -85, -9, -84, -94, -116, 6, -67, 38, 32, -67, 35, 35, 42, 111, 76, 90, 44, 79, -51, 50, -118, -25, 42, -66, -8, -2]
Convert an array of bytes into an array of characters representing the hexadecimal values of each byte in order in lower case. That's your Sign value6ac07edaf4e4abf7aca28c06bd2620bd23232a6f4c5a2c4fcd328ae72abef8fe

Code Examples

Node.js

const crypto = require('crypto');
 
const getSign = (payload, privateKey) => {
    const hash = new crypto.createHash('sha256');
    return hash.update(payload + privateKey).digest('hex');
};
 
console.log(getSign('{"orderId":"12345","transactionId":"advgjq-1ba7ak-asdhja8","orderStatus":"Success"}', "DOSsQcRP9NnzFJxVQL4W"));

Python

import hashlib
 
def get_sign(payload, privateKey):
    valueToHash = payload + privateKey
    return hashlib.sha256(valueToHash.encode()).hexdigest()
     
     
print(get_sign('{"orderId": "12345", "transactionId": "advgjq-1ba7ak-asdhja8", "orderStatus": "Success"}', 'DOSsQcRP9NnzFJxVQL4W'));

Java

public String getSign(String payload, String privateKey) {
    // Use org.apache.commons.codec.digest.DigestUtils
    return DigestUtils.sha256Hex(payload + privateKey);
}
 
System.out.println(getSign("{\"orderId\": \"12345\", \"transactionId\": \"advgjq-1ba7ak-asdhja8\", \"orderStatus\": \"Success\"}", "DOSsQcRP9NnzFJxVQL4W"));

OpenAPI

OpenAPI is available here.

Create transaction

Request

Post request to:

/api/v1/ecommerce/request

Requests should be signed.

ParameterRequiredExampleDescription
amountyes42.87
customerNameyesJacob
customerEmailyes[email protected]
storeNameyesStore24
merchantIdyes[email protected]merchant email (login)
currencyyesAUDThis is the fiat currency we settle in with you.
orderIdyes12345Id generated on the merchant side. Will be returned in webhook payload.
callbackUrlRedirectno<https://yourwebsite.example.com/transaction/status>Where to redirect once the payment is pending (or cancelled if callbackCancelUrlRedirect is blank).
If this value is provided, it will override the fallback option provided in the general settings accessed through the merchant dashboard.
callbackCancelUrlRedirectno<https://yourwebsite.example.com/cancel>Where to redirect once the payment is cancelled. Defaults to callbackUrlRedirect if not provided
webHookUrlno<https://yourwebsite.example.com/api/tx-update>Where to post updates about a payment. These will be posted with every payment change. This callback is guaranteed to happen before we redirect the user back.
If this value is provided, it will override the fallback option provided in the general settings accessed through the merchant dashboard.
Setting this value in the general settings is also optional. If neither place is filled in then the webhook will not be sent.
securityTokennoBasic dXNlcjpwYXNzd29yZA==This token will be sent back with the callback in Authorization header.
(Authorization: [type] [credentials])
When creating the token, both 'type' and 'credentials' are required.
Header NameRequiredExampleDescription
x-api-signatureyes44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8aSHA 256 of stringify payload + private key

Response

Status 200: redirect url

Status 401: Unauthorised Request Check your public and private keys, check signing process.

Transactions status updates

Webhook

Webhook will be send to webHookUrl which was provided in the create transaction request step.

Webhook x-api-signature header should be verified on the merchant side with private key.

Webhook Authorization header should be verified on the merchant side (if used).

Webhook will be sent once for every status update.

Merchant should respond with status OK (200). If RelayPay does not receive status OK - it will retry sending the webhook every 1 minute until status OK is received for a total of 5 retries.

Webhook payload example:

{
  "orderId": "12345",
  "transactionId": "advgjq-1ba7ak-asdhja8",
  "orderStatus": "Success",
  "customerEmail": "[email protected]",
  "customerName": "John Doe",
  "amount": 42.05
}

Header x-api-signature = dbda6873028004858d93d93bc4c7e751019cf4aad0577922d119ed69f5553650

Header Authorisation = Basic dXNlcjpwYXNzd29yZA==

Possible statuses:

  • Cancelled - cancelled by customer
  • Success - transaction successfully completed
  • Failed - transaction failed
  • Pending - transaction not completed yet.
  • Expired - transaction not completed yet.

Get transactions list

/api/v1/merchant/transaction/history

Method: GET

Params:

  • page - number of page
  • size - amount of the elements on the page

Response example:

{
  "totalPages": 0,
  "totalElements": 0,
  "size": 0,
  "content": [
    {
      "orderId": "12345",
      "transactionId": "advgjq-1ba7ak-asdhja8",
      "orderStatus": "Cancelled",
      "customerEmail": "[email protected]",
      "customerName": "John Doe",
      "currency": "AUD",
      "amount": 42.05
    }
  ],
  "number": 0,
  "sort": {
    "unsorted": true,
    "sorted": true,
    "empty": true
  },
  "numberOfElements": 0,
  "first": true,
  "last": true,
  "pageable": {
    "offset": 0,
    "sort": {
      "unsorted": true,
      "sorted": true,
      "empty": true
    },
    "paged": true,
    "unpaged": true,
    "pageNumber": 0,
    "pageSize": 0
  },
  "empty": true
}

Get merchant transaction

/api/v1/merchant/transaction

Method: GET

Params:

  • orderId - unique merchant orderId

Response example:

{
  "orderId": "12345",
  "transactionId": "advgjq-1ba7ak-asdhja8",
  "orderStatus": "Success",
  "customerEmail": "[email protected]",
  "customerName": "John Doe",
  "currency": "AUD",
  "amount": 42.05
}

Possible statuses:

  • Cancelled - cancelled by customer
  • Success - transaction successfully completed
  • Failed - transaction failed
  • Pending - transaction not completed yet.

public String getSign(String stringifiedPayload, String privateKey) {
    // Use org.apache.commons.codec.digest.DigestUtils
    return DigestUtils.sha256Hex(stringifiedPayload + privateKey);
}
 
System.out.println(getSign("{\"orderId\": \"12345\", \"transactionId\": \"advgjq-1ba7ak-asdhja8\", \"orderStatus\": \"Success\"}", "DOSsQcRP9NnzFJxVQL4W"));

Verify webhook

RelayPay will send you webhook call with status updates. Request contains header x-api-signature.

To verify authenticity and be sure that request was send from RelayPay you should take stringified request’s payload, concatenate private key and take SHA-256 hash from it. Then just compare result with the value of Sign header from request.

Another header sent is Authorization if there was a securityToken provided with the transaction request.

Testing

By default you will receive Pending and then Complete transaction status.

For testing your backend, you can induce the following responses by sending the following customerEmail on the first step:

CustomerEmailCreate tx responseTx status
[email protected]Status 200Cancelled
[email protected]Status 200Pending → Failed
[email protected]Status 200Pending → Expired