Server Setup
Server-side integration of the Zumo platform (from here on referred to as Client API) requires components for bi-directional communication with ZumoKit API service.
Authentication
To offer granular security and homogenous authentication arhitecture our security model uses JSON Web Tokens (JTW). Because client communicates to ZumoKit API through his own API and through ZumoKit SDK, authentication is required on both parties.
Authentication of API
Client must use app-id and app-secret which are available inside ZumoKit integration dashboard to authenticate and receive his JWT which is then used for all Client API - ZumoKit API communication.
Endpoint: /token/
Request method: POST
Media type: application/json
Type: object
Headers
Content-Type: application/json
curl -X POST \
https://kit.sandbox.zumo.money/v1/token/ \
-H 'Content-Type: application/json' \
-d '{
"appId": "{{app-id}}",
"appSecret": "{{app-secret}}"
}'
use GuzzleHttp\Psr7\Request;
$headers = ['Content-Type' => 'application/json'];
$body = json_encode([
'appUserId' => '{{app-user-id}}'
'appSecret' => "{{app-secret}}"
]);
$request = new Request('POST', 'https://kit.sandbox.zumo.money/v1/token/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'POST',
url: 'https://kit.sandbox.zumo.money/v1/token/',
headers:
{
'Content-Type': 'application/json'
},
body:
{
appId: '{{app-id}}',
appSecret: '{{app-secret}}'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
If access token is about to expire you can request a new set with your refresh token. Make sure that you are using old token in authorization header.
curl -X POST \
https://kit.sandbox.zumo.money/v1/token/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
-d '{
"refreshToken": "{{refresh-token}}"
}'
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$body = json_encode([
'refreshToken' => '{{refresh-token}}'
]);
$request = new Request('POST', 'https://kit.sandbox.zumo.money/v1/token/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'POST',
url: 'https://kit.sandbox.zumo.money/v1/token/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{
refreshToken: '{{refresh-token}}'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Response options
{
"accessToken": "{{access-token}}",
"expiresIn": "{{expiration}}",
"refreshToken": "{{refresh-token}}}"
}
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Authentication of ZumoKit SDK user
Every unique user communicating to ZumoKit API through ZumoKit SDK needs to be authenticated. As trust is already established between Client API and ZumoKit API, Client API can authenticate user directly. Response returned on this endpoint is JWT unique to the user. This token needs to be propagated to the application which then uses it when authenticating the user with ZumoKit SDK.
Endpoint: /app-users/{{app-user-id}}/token/
Request method: POST
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X POST \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
-d '{
"appUserId": "{{app-user-id}}"
}'
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$body = json_encode([
'appUserId' => '{{appUserId}}'
]);
$request = new Request('POST', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'POST',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{
appUserId: '{{app-user-id}}'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
If users token is about to expire you can request a new set with your refresh token.
curl -X POST \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
-d '{
"refreshToken": "{{app-user--refresh-token}}"
}'
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$body = json_encode([
'refreshToken' => '{{app-user--refresh-token}}'
]);
$request = new Request('POST', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'POST',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/token/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{
refreshToken: '{{app-user--refresh-token}}'
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Response options
{
"accessToken": "{{app-user--access-token}}",
"expiresIn": "{{expiration}}",
"refreshToken": "{{ap-user--refresh-token}}}"
}
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
User management
User management is domain of Client API. For user to be able to interact with ZumoKit SDK, he will firstly need to be created by Client API calling directly ZumoKit API. After user is created and authenticated, he is able to interact with ZumoKit API directly through ZumoKit SDK.
Creating new user
Before authenticating user and allowing him to use ZumoKit SDK, that user needs to be registered at ZumoKit API.
Endpoint: /app-users/{{app-user-id}}/
Request method: POST
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X POST \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
-d '{
"appUserId": "{{app-user-id}}"
}'
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$body = json_encode([
'appUserId' => '{{app-user-id}}'
]);
$request = new Request('POST', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'POST',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{
appUserId: "{{app-user-id}}"
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Response options
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:30:55.570Z",
"modifiedAt": "2020-04-15T13:30:55.570Z",
"appUserId": "string",
"synced": false,
"wallet": {}
}
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Get existing user
Client API can request all data related to specific user.
Endpoint: /app-users/{{app-user-id}}/
Request method: GET
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X GET \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$request = new Request('GET', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/', $headers);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'GET',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Response options
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:43:34.666Z",
"modifiedAt": "2020-04-15T13:43:34.666Z",
"appUserId": "string",
"synced": false,
"wallet": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:43:34.666Z",
"modifiedAt": "2020-04-15T13:43:34.666Z",
"accounts": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:43:34.666Z",
"modifiedAt": "2020-04-15T13:43:34.666Z",
"network": "string",
"address": "string",
"path": "string",
"coin": "string",
"symbol": "string",
"version": "string"
}
]
}
}
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Update existing user
Currently only property that can be updated on single user is synchronised status.
Endpoint: /app-users/{{app-user-id}}/
Request method: PUT
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X PUT \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
-d '{
"appUserId": "{{app-user-id}}",
"synchronised": true
}'
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$body = json_encode([
'appUserId' => '{{app-user-id}}'
'synchronised' => true
]);
$request = new Request('PUT', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/', $headers, $body);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'PUT',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{
appUserId: "{{app-user-id}}",
synchronised: true
},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
OK
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Delete existing user
If user is removed from Client API it makes sense to remove him from ZumoKit API. This action is not reversable so caution is required.
Endpoint: /app-users/{{app-user-id}}/
Request method: DELETE
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X DELETE \
https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/ \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$request = new Request('DELETE', 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/', $headers);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'DELETE',
url: 'https://kit.sandbox.zumo.money/v1/app-users/{{app-user-id}}/',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body: {},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
OK
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Get all user
If needed Client API can request all users associated with his account. Setting query string synced=false returns only users that we have not marked as synced yet.
Endpoint: /app-users?synced=false
Request method: GET
Media type: application/json
Type: object
Headers
Authorization: Bearer {{access-token}}
Content-Type: application/json
curl -X GET \
https://kit.sandbox.zumo.money/v1/app-users?synced=false \
-H 'Authorization: Bearer {{access-token}}' \
-H 'Content-Type: application/json' \
use GuzzleHttp\Psr7\Request;
$headers = [
'Authorization' => 'Bearer {{access-token}}',
'Content-Type' => 'application/json'];
$request = new Request('GET', 'https://kit.sandbox.zumo.money/v1/app-users?synced=false', $headers);
echo $response->getStatusCode();
echo $response->getBody();
var request = require("request");
var options = {
method: 'GET',
url: 'https://kit.sandbox.zumo.money/v1/app-users?synced=true',
headers:
{
'Content-Type': 'application/json',
'Authorization': 'Bearer {{access-token}}'
},
body:
{},
json: true };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
{
"page": {
"limit": 0,
"offset": 0,
"all": 0
},
"users": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:49:39.238Z",
"modifiedAt": "2020-04-15T13:49:39.238Z",
"appUserId": "string",
"synced": false,
"wallet": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:49:39.238Z",
"modifiedAt": "2020-04-15T13:49:39.238Z",
"accounts": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"createdAt": "2020-04-15T13:49:39.238Z",
"modifiedAt": "2020-04-15T13:49:39.238Z",
"network": "string",
"address": "string",
"path": "string",
"coin": "string",
"symbol": "string",
"version": "string"
}
]
}
}
]
}
{
"statusCode": 400,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 401,
"type": "invalid_request_error",
"message": "Entity not found."
}
{
"statusCode": 404,
"type": "invalid_request_error",
"message": "Entity not found."
}
Webhooks
To automate the handling of user interactions with ZumoKit SDK and synchronization of that data between Client API - ZumoKit API, you will need to implement a webhook listener. Once you have determined URL of the listener you can configure it in the ZumoKit Integration dashboard.
Security
It is important to check, that all webhook calls originate from Zumo.
You can secure your webhook listener by following this measures:
- Make sure you have secure SSL server (ZumoKit will only call https urls)
- Verify the X-SIGNATURE headers on the webhook call
- Restrict your webhook listener to only accept calls from ZumoKit IP range
Call structure
You will receive calls with different information, but all of them share same basic structure.
- eventType: Standardized type of the event
- subjectType: Entity that fired this event
- subject: Payload of the event
User account updated event
This is most basic and at the same time the most important one. When user creates new wallet with the help of ZumoKit SDK, that data is encrypted and sent directly to ZumoKit API. If your app needs to be notified when user created a new wallet or you need wallet addresses, you can subscribe to this event and you will be notified about it instantly.
Payload sent on webhook call:
{
"eventType": "appUser.wallet.accounts.updated",
"subjectType": "appUser",
"subject": {
"appUserId": "00000000-0000-0000-0000-000000000000",
"createdAt": "2020-01-15 09:03:13"
"updatedAt": "2020-01-15 09:03:13"m
"wallet": {
"accounts": [
{
"id": "00000000-5555-0000-0000-000000000000"
"createdAt": "2020-01-15 10:22:24"
"updatedAt": "2020-01-15 10:22:24"
}
]
}
},
}