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 ([email protected]) 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

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":"[email protected]","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":"[email protected]","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

v1 version 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-Type the content type that sender has been sent

  • Header gj-signature the header signature that provided by GalaxyJoy Webhook. It's value contains:

    • t=1681309224070 The timestamps sender has been sent. The receiver side can use it to preventing replay attacks

    • v1=6f85d9fc3bb10758ab35d84c05387c18bde44d8f446dc4af2ad6a2b335da79d0 The webhook version and signature request payload. The receiver side must use it to verify the body before handling it

  • Body event_type The event type that can determine an object's change event

  • Body event_id The event unique id

  • Body timestamp The timestamp in second

  • Body body The 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

Last updated

Was this helpful?