Interlace API Notifications

This document list and describes the events and data models used in Interlace Notifications.

WebHook

Interlace uses Webhooks to notify a single callback URL provided by the client in a specified format. Interlace will send different payloads according to the scenarios described further below, and the trigger event can be understood in terms of the businessType field in the payload body.

After receiving a notification, needs to return a reply packet within 5 seconds. Otherwise, Interlace considers the notification failed and sends the notification repeatedly.

The same notification may be sent multiple times, and duplicate notifications must be handled correctly. If it has been processed, return success directly to Interlace.

The client should return the specified return code. If no corresponding return code is received after sending the callback URL, the Interlace system will consider the push unsuccessful. The return fields are as follows:

FieldTypeDescription
receivedbooleanReceiving identifier

Example:

👍

{

"received": true

}

Retry interval

Retry numberIntervalRetry numberInterval
110 seconds97 minutes
230 seconds108 minutes
31 minute119 minutes
42 minutes1210 minutes
53 minutes1320 minutes
64 minutes1430 minutes
75 minutes151 hour
86 minutes162 hours

Common Considerations

All current implementations of notification messages have the following attributes:

NameTypeDescriptionSample
idUUIDnotification identifier32b0216b-66d9-498b-a4bc-17612d9cb6cd
businessTypestringThe businessType of notificationAssetsDeposit
signstringsignature055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd

Account Notification

AccountRegistered

When you create a subaccount, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "AccountRegistered",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Account Object.

KYC

When you create a subaccount through KYC, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "KYC",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Account Object.

FaceAuthentication

When submitting face authentication, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "FaceAuthentication",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a FaceAuthentication Object.

Quantum Card Notification

CreateCard

Create a quantum card, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CreateCard",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Card Object.

FrozenCard

Frozen quantum card, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "FrozenCard",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Card Object.

UnfrozenCard

Unfrozen quantum card, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "UnfrozenCard",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Card Object.

DeleteCard

Delete quantum card, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "DeleteCard",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Card Object.

CardStateChange

When the quantum card state changes, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CardStateChange",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Card Object.

CardTransaction

When the quantum card generates a transaction, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CardTransaction",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a CardTransaction Object.

FrozenAmount

Frozen the quantum card amount, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "FrozenAmount",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a CardTransaction Object.

UnfrozenAmount

Unfrozen the quantum card amount, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "UnfrozenAmount",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a CardTransaction Object.

BudgetTransaction

When a budget generates a transaction, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "BudgetTransaction",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a BudgetTransaction Object.


Card3dsOtp

Quantum card 3DS verification,you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "Card3dsOtp",
    "data": {
        "cardId": "7c9cde3a-12e6-4320-b11a-835d6b6e35db",
        "accountId": "b5d2fb72-b8bd-408b-ab95-91ef03a02bd6",
        "currency": "USD",
        "amount": 100,
        "cardNumber": "4931-93xx-xxxx-1234",
        "otp": "123456", // 3DS OTP
        "detail": "Apple Pay" // Merchant information
    },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

ThreeDomainSecureForwarding

This Webhook is triggered when you need to make a 3DS decision using an alternative to the default SMS,you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "ThreeDomainSecureForwarding",
    "data": {
        "cardId": "7c9cde3a-12e6-4320-b11a-835d6b6e35db",
        "accountId": "b5d2fb72-b8bd-408b-ab95-91ef03a02bd6",
        "actionId": "1824275858735009795",
        "currency": "USD",
        "amount": 100,
        "cardNumber": "4931-93xx-xxxx-1234",
        "detail": "Apple Pay", // Merchant information
        "timestamp": "1725350485682", // Consumption time - millisecond stamp
        "expirationTime": "1725350785682", // Expiration time - millisecond stamp
        "url": "https://URL to submit 3DS information"
    },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

Overspend

Budget or quantum card overspend (push at 0:00 daily),you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "Overspend",
    "data": { "fileUrl":"file resource url(Valid for 7 days)"},
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

CardBinStatus

When the card bin is maintained/restored, you should see the notification message shown below.。

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CardBinStatus",
    "data": {
        "status": "Operation", // {Maintenance, Operation}
        "cardBins": ["433451","441112","489683"], // bin list
        "time":"2024-03-05T03:39:08.000Z" // Maintenance time /Operation time
    },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

Global Account Notification

CreateGlobalAccount

Open global accounts in compliance, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CreateGlobalAccount",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a BankAccount Object.

GlobalAccountTransaction

When a global account generates a transaction, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "CreateGlobalAccount",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a GlobalAccountTransaction Object.

Crypto Asset Notification

AssetsDeposit

Once you process a successful deposit, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "AssetsDeposit",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Deposit Object.

AssetsWithdrawal

Once you process a successful withdrawal, you should see a notification message on your local server shell that looks like the following.

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "businessType": "AssetsWithdrawal",
    "data": { ... },
    "sign": "055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd"
}

The data payload will be a Withdrawal Object.

Signature

Each request initiated by Interlace contains a sign parameter that can be used to verify the authenticity of the request from Interlace. For each request, the data of the data parameter is fetched and processed through the HMAC-SHA256 hash function.

  1. Set of parameters to be signed
const params = {
    "id": "ee74c872-8173-4b67-81b1-5746e7d5ab88",
    "accountId": null,
    "holderId": "d2bd6ab3-3c28-4ac7-a7c4-b7eed5eee367",
    "currency": "USD",
    "settlementCurrency": null,
    "counterparty": "SAILINGWOOD;;US;1800948598;;091000019",
    "transactionAmount": 11,
    "fee": 0,
    "businessType": "Inbound",
    "status": "Closed",
    "transactionTime": "2021-11-22T07:34:10.997Z",
    "transactionId": "124d3804-defa-4033-9f30-1d8b0468e506",
    "clientTransactionId": null,
    "createTime": "2021-11-22T07:34:10.997Z",
    "appendFee": 0,
};
  1. The parameter set key to be signed is sorted in ascending order according to "ASCII code of the first character of the string" (if the ASCII code value is the same in the sorting process, the next digit is incremented in sequence for comparison).
const keys = Object.keys(params);
keys.sort();
  1. Concatenates a string, and empty values are filled with empty strings
accountId=&appendFee=0&businessType=Inbound&clientTransactionId=&counterparty=SAILINGWOOD;;US;1800948598;;091000019&createTime=2021-11-22T07:34:10.997Z&currency=USD&fee=0&holderId=d2bd6ab3-3c28-4ac7-a7c4-b7eed5eee367&id=ee74c872-8173-4b67-81b1-5746e7d5ab88&settlementCurrency=&status=Closed&transactionAmount=11&transactionId=124d3804-defa-4033-9f30-1d8b0468e506&transactionTime=2021-11-22T07:34:10.997Z
  1. CLIENT_SECRET is used to perform hmac-sha256 signature on the concatenated string, and the hexadecimal encoding is used to obtain signature.
const hmac = crypto.createHmac('sha256', '25d55ad283aa400af464c76d713c07ad');
const sign = hmac.update('Concatenates a string').digest('hex');

Example Code

const crypto = require('crypto');

function joinStr(params) {
    const keys = Object.keys(params);
    keys.sort();
    const result = [];
    for (const key of keys) {
        let val = params[key];
        if (val == null) {
            val = '';
        } else if (typeof val === 'object') {
            if (!Array.isArray(val)) {
                const fields = Object.keys(val);
                fields.sort();
                const res = {};
                for (const field of fields) {
                    res[field] = val[field];
                }
                val = res;
            }
            val = JSON.stringify(val);
        }
        result.push(`${key}=${val}`);
    }
    return result.join('&');
}

const params = {
    'createTime': '2023-05-31T07:29:46.784Z',
    'budgetId': null,
    'provider': 'PrepaidCard_493728',
    'currency': 'USD',
    'qbitCardNoLastFour': '1234',
    'id': 'b9ce056b-c1f8-4f19-b014-d7be02a54598',
    'status': 'Active',
    'useType': '79f22263-a3fe-4347-8a40-2af6bf422839',
    'label': 'ce08100b-fca8-4a13-bbfc-c381aeaec5d0',
    'balanceId': 'ab43462f-93b3-4540-8601-11d759948ee7',
    'cardAddress': {
        'country': 'US',
        'postalCode': '94402',
        'addressLine2': '',
        'addressLine1': '20 Barneson ave',
        'state': 'California',
        'city': 'San Mateo'
    },
    'accountId': '01eba490-5f9c-48a6-aa2d-7bcfdff0d720',
    'token': '0ef85b24-866f-4c03-a7e8-459e3742642b',
    'userName': 'test test'
};

const hmac = crypto.createHmac('sha256', '25d55ad283aa400af464c76d713c07ad');
const sign = hmac.update(joinStr(params)).digest('hex');
console.log(sign);
// => 178997e5960603afc573a28743d1680e3719a400e83936076f4dae4cb123a35a
package money.interlace.demo;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class Signature {
    public static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        Formatter formatter = new Formatter(sb);
        for (byte b : bytes) {
            formatter.format("%02x", b);
        }
        return sb.toString();
    }

    public static byte[] sign(String str, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
        Key sk = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
        Mac mac = Mac.getInstance(sk.getAlgorithm());
        mac.init(sk);
        return mac.doFinal(str.getBytes());
    }

    public static String joinStr(Map<String, Object> data) {
        String[] keys = data.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object val = data.get(key);
            if (val == null) {
                val = "";
            }
            if (val instanceof Map<?, ?>) {
                val = JSON.toJSONString(new TreeMap<>((Map<?, ?>) val), JSONWriter.Feature.WriteMapNullValue);
            }
            if (val instanceof Collection<?> || val instanceof Object[]) {
                val = JSON.toJSONString(val);
            }
            sb.append(key).append("=").append(val).append("&");
        }
        String str = sb.toString();
        return str.substring(0, str.length() - 1);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
        Map<String, String> address = new HashMap<>();
        address.put("addressLine1", "20 Barneson ave");
        address.put("addressLine2", "");
        address.put("city", "San Mateo");
        address.put("country", "US");
        address.put("postalCode", "94402");
        address.put("state", "California");

        Map<String, Object> data = new HashMap<>();
        data.put("id", "b9ce056b-c1f8-4f19-b014-d7be02a54598");
        data.put("accountId", "01eba490-5f9c-48a6-aa2d-7bcfdff0d720");
        data.put("token", "0ef85b24-866f-4c03-a7e8-459e3742642b");
        data.put("status", "Active");
        data.put("currency", "USD");
        data.put("provider", "PrepaidCard_493728");
        data.put("userName", "test test");
        data.put("createTime", "2023-05-31T07:29:46.784Z");
        data.put("qbitCardNoLastFour", "1234");
        data.put("label", "ce08100b-fca8-4a13-bbfc-c381aeaec5d0");
        data.put("useType", "79f22263-a3fe-4347-8a40-2af6bf422839");
        data.put("balanceId", "ab43462f-93b3-4540-8601-11d759948ee7");
        data.put("budgetId", null);
        data.put("cardAddress", address);

        String signStr = bytesToHex(sign(joinStr(data), "25d55ad283aa400af464c76d713c07ad"));

        System.out.println(signStr);
        // => 178997e5960603afc573a28743d1680e3719a400e83936076f4dae4cb123a35a
    }
}
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"
)

func getKeys(m map[string]any) []string {
	j := 0
	keys := make([]string, len(m))
	for k := range m {
		keys[j] = k
		j++
	}
	return keys
}

func joinStr(params map[string]any) string {
	keys := getKeys(params)
	sort.Strings(keys)
	var result []string
	for _, key := range keys {
		val := params[key]
		if val == nil {
			val = ""
		}
		t := reflect.TypeOf(val)
		switch t.Kind() {
		case reflect.Array, reflect.Map, reflect.Slice:
			bytes, _ := json.Marshal(&val)
			val = string(bytes)
			break
		default:
		}
		result = append(result, fmt.Sprintf("%s=%s", key, fmt.Sprintf("%v", val)))
	}
	return strings.Join(result, "&")
}

func sign(message string, secret string) string {
	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	return hex.EncodeToString(h.Sum(nil))
}

func main() {
	params := map[string]any{
		"createTime":         "2023-05-31T07:29:46.784Z",
		"budgetId":           nil,
		"provider":           "PrepaidCard_493728",
		"currency":           "USD",
		"qbitCardNoLastFour": "1234",
		"id":                 "b9ce056b-c1f8-4f19-b014-d7be02a54598",
		"status":             "Active",
		"useType":            "79f22263-a3fe-4347-8a40-2af6bf422839",
		"label":              "ce08100b-fca8-4a13-bbfc-c381aeaec5d0",
		"balanceId":          "ab43462f-93b3-4540-8601-11d759948ee7",
		"cardAddress": map[string]any{
			"country":      "US",
			"postalCode":   "94402",
			"addressLine2": "",
			"addressLine1": "20 Barneson ave",
			"state":        "California",
			"city":         "San Mateo",
		},
		"accountId": "01eba490-5f9c-48a6-aa2d-7bcfdff0d720",
		"token":     "0ef85b24-866f-4c03-a7e8-459e3742642b",
		"userName":  "test test",
	}
	fmt.Println(sign(joinStr(params), "25d55ad283aa400af464c76d713c07ad"))
	// => 178997e5960603afc573a28743d1680e3719a400e83936076f4dae4cb123a35a
}

import json
import hashlib
import hmac


def sign(message, secret):
    encryption = hmac.new(bytes(secret, encoding='UTF-8'), bytes(message, encoding='UTF-8'), hashlib.sha256)
    return encryption.hexdigest()


def join_str(origin):
    keys = list(origin.keys())
    keys.sort()
    content = []
    for key in keys:
        val = origin[key]
        if val is None:
            val = ''
        if isinstance(val, dict) or isinstance(val, list):
            val = json.dumps(val, sort_keys=True, ensure_ascii=False, separators=(',', ':'))
        content.append(key + '=' + str(val))
    return '&'.join(content)


if __name__ == '__main__':
    data = {
        'createTime': '2023-05-31T07:29:46.784Z',
        'budgetId': None,
        'provider': 'PrepaidCard_493728',
        'currency': 'USD',
        'qbitCardNoLastFour': '1234',
        'id': 'b9ce056b-c1f8-4f19-b014-d7be02a54598',
        'status': 'Active',
        'useType': '79f22263-a3fe-4347-8a40-2af6bf422839',
        'label': 'ce08100b-fca8-4a13-bbfc-c381aeaec5d0',
        'balanceId': 'ab43462f-93b3-4540-8601-11d759948ee7',
        'cardAddress': {
            'country': 'US',
            'postalCode': '94402',
            'addressLine2': '',
            'addressLine1': '20 Barneson ave',
            'state': 'California',
            'city': 'San Mateo'
        },
        'accountId': '01eba490-5f9c-48a6-aa2d-7bcfdff0d720',
        'token': '0ef85b24-866f-4c03-a7e8-459e3742642b',
        'userName': 'test test'
    }
    sign_str = sign(join_str(data), '25d55ad283aa400af464c76d713c07ad')
    print(sign_str)
    # => 178997e5960603afc573a28743d1680e3719a400e83936076f4dae4cb123a35a


function sign($message, $secret): string
{
    return hash_hmac("sha256", $message, $secret);
}

function join_str($data): string
{
    ksort($data);
    $content = array();
    foreach ($data as $key => $val) {
        if (is_null($val)) {
            $val = "";
        }
        if (is_array($val)) {
            ksort($val);
            $val = json_encode($val);
        }
        $content[] = "$key=$val";
    }

    echo '<br>';
    echo join("&", $content);
    echo '<br>';
    return join("&", $content);
}

function test(): string
{
    $params = array(
        "createTime" => "2023-05-31T07:29:46.784Z",
        "budgetId" => null,
        "provider" => "PrepaidCard_493728",
        "currency" => "USD",
        "qbitCardNoLastFour" => "1234",
        "id" => "b9ce056b-c1f8-4f19-b014-d7be02a54598",
        "status" => "Active",
        "useType" => "79f22263-a3fe-4347-8a40-2af6bf422839",
        "label" => "ce08100b-fca8-4a13-bbfc-c381aeaec5d0",
        "balanceId" => "ab43462f-93b3-4540-8601-11d759948ee7",
        "cardAddress" => array(
            "country" => "US",
            "postalCode" => "94402",
            "addressLine2" => "",
            "addressLine1" => "20 Barneson ave",
            "state" => "California",
            "city" => "San Mateo"
        ),
        "accountId" => "01eba490-5f9c-48a6-aa2d-7bcfdff0d720",
        "token" => "0ef85b24-866f-4c03-a7e8-459e3742642b",
        "userName" => "test test"
    );
    return sign(join_str($params), "25d55ad283aa400af464c76d713c07ad");
}
echo test();
// => 178997e5960603afc573a28743d1680e3719a400e83936076f4dae4cb123a35a