Partner Integration
Summary
- Version v1
- Release date 30/04/2023
GalaxyJoy Webhook provides webhook events that send to your endpoint with gj-signature in the header. You can receive webhook events in which an object changes and secured with an e-signature between sender and receiver.
Before you can verify signatures, you need to register the endpoint by contact to GalaxyJoy (integration@galaxyjoy.vn) and receive an endpoint's SecretKey. If you use multiple endpoints, you must obtain a SecretKey for each one you want to verify signatures on.
Integration guidelines
Event Type
All events always contain event type as an attribute event_type. Event types are represented by actions in which objects occur.
Each event_type contains two parts including object name (user) and action (created)
List available event types:
| Event Type | Description | Available | Payload |
|---|---|---|---|
| Event Type | Description | Available | Payload |
| user.created | User created | Yes | {"timestamp":1691047856,"event_id":"110de1b1-818c-4ec5-8a67-e321e8d72274","event_type":"user.created","data":{"id":"6d2d6da9-6341-43a9-9f69-b56c3e980ae1","email":null,"partner":"GJOY","username":"+84330012221","last_name":"Quinn","member_id":"SJ1047851528","first_name":"Quinn","sponsor_id":"4","custom_data":"{"terms_and_condition_date":"03/08/2023","language":"vi"}","country_code":"+84","phone_number":"+84330012221","date_of_birth":"03-08-1998","partner_information":[],"date_of_birth_format":null},"version":"v1"} |
| user.updated-profile | User profile updated | Yes | {"timestamp":1691048584,"event_id":"2ad0bb5b-cf12-4b77-959f-9cf387cade27","event_type":"user.updated-profile","data":{"id":"6d2d6da9-6341-43a9-9f69-b56c3e980ae1","email":"uinquinuqbt@gmail.com","partner":"GJOY","username":"+84330012221","last_name":"Quinn","member_id":"SJ1047851528","first_name":"Quinn","sponsor_id":"4","custom_data":"{"terms_and_condition_date":"03/08/2023","language":"vi"}","country_code":"+84","phone_number":"+84330012221","date_of_birth":"03-08-1998","partner_information":[],"date_of_birth_format":null},"version":"v1"} |
| user.updated-ekyc | User ekyc updated | Yes | {"timestamp":1690950667,"event_id":"009a336c-8b69-4f73-8ae8-f03a8403bf08","event_type":"user.updated-ekyc","data":{"id":"c382687b-9d80-48f2-82d3-9e53e0c1e225","email":"trannewflow@yopmail.com","partner":"HDB","username":"+84796740907","last_name":"TRAN","member_id":"SJ8669727138","first_name":"ONBOARDSECONDAUGUST","sponsor_id":"9","custom_data":"{"referral_code":"null"}","country_code":"+84","phone_number":"+84796740907","date_of_birth":"01-01-2000","partner_information":[],"date_of_birth_format":null,"user_identity_details":"{"status":"APPROVED","user_id":"c382687b-9d80-48f2-82d3-9e53e0c1e225","ekyc_provider":"HDB","nation_id_card":{"name":"TRANONBOARDSECONDAUGUST","dob":"2000-01-01","province":null,"gender":null,"doe":null,"nationality":"VIETNAMESE","country":"VN","doi":null,"url1":"","url2":"","id_number":"010010120","home_town":"LoremIpsumissimplydummytextoftheprintingandtypesettingindustry","permanent_address":"LoremIpsumissimplydummytextoftheprintingandtypesettingindustry","place_of_issue":null,"pod":null},"passport":null,"face_url1":"","face_url2":""}"},"version":"v1"} |
| user.updated-balance | User balance updated | Yes | {"timestamp":1691047862,"event_id":"25ffb869-dff8-4c24-b837-0e6013e4768b","event_type":"user.updated-balance","data": { "id": "604e3710-ddb0-42ff-86af-6582aa63907c", "balance": 200, "member_id": "SJ2026465076", "phone_number": "+84931119420", "balance_updated_at": "2024-04-05T03:12:04.642Z" },"version":"v1"} |
| user.updated-tier | User tier updated | Yes | {"timestamp":1691047862,"event_id":"25ffb869-dff8-4c24-b837-0e6013e4768b","event_type":"user.updated-tier","data":{ "id": "3c51da9e-d400-4747-9a11-7c537567ccd3", "member_id": "SJ5104023461", "tier_code": "Tier 3", "tier_name": "Gold", "phone_number": "+84904539431", "tier_end_date": "2025-04-30T16:59:59.000Z" },"version":"v1"} |
| user.activated | User activated | Yes | {"timestamp":1691047863,"event_id":"a0b34e4d-fde7-4b5c-af6e-e6310b123c43","event_type":"user.updated-profile","data":{"id":"6d2d6da9-6341-43a9-9f69-b56c3e980ae1","phone_number":"+84330012221"},"version":"v1"} |
| user.deleted | User deleted | Yes | {"timestamp":1691047862,"event_id":"25ffb869-dff8-4c24-b837-0e6013e4768b","event_type":"user.deleted","data":{"id":"f368e6f1-55d5-4cc9-9cca-990a7a2c5f3d","phone_number":"+84387255925","comment":"Userrequest","member_id":"SJ8029301625"},"version":"v1"} |
Signature Version
v1version is the first algorithms which GalaxyJoy Webhook support by default
| Version | Description | Available |
| v1 | algorithm using the SHA-256 hashing function. | Yes |
Receiver webhook
Method: POST
Receive headers
"Content-Type": "application/json"
"gj-signature": "t=1681309224070,v1=6f85d9fc3bb10758ab35d84c05387c18bde44d8f446dc4af2ad6a2b335da79d0"Receive body
{
"event_type": "user.created",
"event_id": "8ee4419d-04d8-48e9-a164-f0559b14cdf1",
"timestamp": 1681729294,
"body": {}
}- the Header
Content-Typethe content type that sender has been sent - Header
gj-signaturethe header signature that provided by GalaxyJoy Webhook. It's value contains:t=1681309224070The timestamps sender has been sent. The receiver side can use it to preventing replay attacksv1=6f85d9fc3bb10758ab35d84c05387c18bde44d8f446dc4af2ad6a2b335da79d0The webhook version and signature request payload. The receiver side must use it to verify the body before handling it
- Body
event_typeThe event type that can determine an object's change event - Body
event_idThe event unique id - Body
timestampThe timestamp in second - Body
bodyThe object payload data
Verify signatures
Normally, client http will receive a raw body when headers
Content-Type: application/json, but some applications can use a middleware that parse body to Json format. So, if your application has been implement body parse json, you need to change your endpoint which register on GalaxyJoy Webhook to receive raw body.Example with NodeJS Express.
Example with NodeJS
// app.js
'use strict';
// This example uses Express to receive webhooks
const express = require('express');
const crypto = require('crypto');
const endpointSecret = 'gj_test_...';
const versionV1 = 'v1';
const app = express();
const signBodyV1 = function(payload, timestamp) {
const hmac = crypto.createHmac('sha256', endpointSecret);
const payloadStr = typeof payload == 'object' ? JSON.stringify(payload) : payload;
const signatureData = `${timestamp}.${versionV1}.${payloadStr}`;
return hmac.update(signatureData).digest('hex');
}
const verifyHeader = function(signatureHeader) {
const [timeData, ...signatureVersions] = signatureHeader.split(',');
if (!timeData || !signatureVersions.length) {
throw new Error('Invalid signature header');
}
const [_t, timestamp] = timeData.split('=');
const signatures = signatureVersions.map((signatureVersion) => {
const [version, signature] = signatureVersion.split('=');
return { version: version, value: signature };
});
return {
signatures: signatures,
timestamp: Number(timestamp),
};
}
const getEvent = function(rawBody, signatureHeader) {
const payload = rawBody instanceof Uint8Array
? new TextDecoder('utf8').decode(rawBody)
: rawBody;
const { signatures, timestamp } = verifyHeader(signatureHeader);
const clientSignature = signBodyV1(payload, timestamp);
const signatureV1 = signatures.find((signature) => signature.version === versionV1);
if (signatureV1.value !== clientSignature) {
throw new Error('Invalid signature')
}
const event = JSON.parse(payload);
return event;
}
// Match the raw body to content type application/json
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
const signatureHeader = request.headers['gj-signature'];
const rawBody = request.body; // remember to use rawBody in the signature verification below
const event = getEvent(rawBody, signatureHeader);
// Return a response with status code 200 to acknowledge receipt of the event
return response.json({receivedEvent: event.event_type});
});
app.listen(3000, () => console.log('Running on port 3000'));Run: node app.js