SkyJoy
HomeDocs
  • 👋Welcome to SkyJoy
  • GETTING STARTED
    • Introduction
    • Ground rules for API
    • Authentication
    • Error handling
  • DEVELOPMENT PROCESS
    • Integration model
    • Batch
  • API REFERENCE
    • Authentication
    • Customer
    • Point
    • Transaction
  • WEBHOOK
    • Partner Integration
  • SINGLE SIGN-ON (SSO)
    • iFrame
    • Regular Web App
    • Single Page App
  • VERSION HISTORY
    • Document changelog
Powered by GitBook
On this page
  • Summary
  • Integration guidelines
  • Event Type
  • Signature Version
  • Receiver webhook
  • Verify signatures
  • Example with NodeJS

Was this helpful?

  1. WEBHOOK

Partner Integration

Last updated 1 year ago

Was this helpful?

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 () 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

user.updated-profile

User profile updated

Yes

user.updated-ekyc

User ekyc updated

Yes

user.updated-balance

User balance updated

Yes

user.updated-tier

User tier updated

Yes

user.activated

User activated

Yes

user.deleted

User deleted

Yes

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:

  • 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

// 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

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

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

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

Example with .

{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
integration@galaxyjoy.vn
preventing replay attacks
NodeJS Express
signature request payload
event type