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:
Field | Type | Description |
---|---|---|
received | boolean | Receiving identifier |
Example:
{
"received": true
}
Retry interval
Retry number | Interval | Retry number | Interval |
---|---|---|---|
1 | 10 seconds | 9 | 7 minutes |
2 | 30 seconds | 10 | 8 minutes |
3 | 1 minute | 11 | 9 minutes |
4 | 2 minutes | 12 | 10 minutes |
5 | 3 minutes | 13 | 20 minutes |
6 | 4 minutes | 14 | 30 minutes |
7 | 5 minutes | 15 | 1 hour |
8 | 6 minutes | 16 | 2 hours |
Common Considerations
All current implementations of notification messages have the following attributes:
Name | Type | Description | Sample |
---|---|---|---|
id | UUID | notification identifier | 32b0216b-66d9-498b-a4bc-17612d9cb6cd |
businessType | string | The businessType of notification | AssetsDeposit |
sign | string | signature | 055144f3c59f2412d5575daee1ecc473b028378d3c3e359970fc96b33314e5bd |
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.
Infinity Card Notification
CreateCard
Create a infinity 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 infinity 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 infinity 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 infinity 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 infinity 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 infinity 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 infinity 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 infinity 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
Infinity 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 infinity 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"
}
Business Account Notification
CreateGlobalAccount
Open Business 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 Business 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.
- 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,
};
- 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();
- 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¤cy=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
- 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
Updated about 1 month ago