Skip to content

API Documentation ​

This page contains detailed documentation for the 2J Game API, including global interface descriptions, error code definitions, amount parameters, game types, and more. By reading this page, developers can understand how to interact with 2J Game servers, how to handle different types of game requests, and how to handle API error codes.

Seamless Wallet Mode and Transfer Wallet Mode are two wallet modes provided by 2J Game. OPs can choose the appropriate mode based on their needs.

Seamless Wallet Mode ​

Seamless Wallet Mode refers to the seamless integration of user account balances between OP and 2J, where users do not need to perform deposits and withdrawals between OP and 2J. When players play on the 2J platform (such as betting and payout actions), 2J will operate the player's account by calling OP's interface.

Interaction Flow ​

uml diagram

Integration Documentation ​

Click to view Seamless Wallet Mode Integration Documentation

Transfer Wallet Mode ​

Transfer Wallet Mode refers to the transfer operation of user account balances between OP and 2J. OP needs to call 2J's interface to perform deposits and withdrawals for player accounts. When players play on the 2J platform (such as betting and payout actions), 2J will directly operate the player's account, and OP can query the player's account balance by calling 2J's interface.

Interaction Flow ​

uml diagram

Integration Documentation ​

Click to view Transfer Wallet Mode Integration Documentation

Global Interface Description ​

Common Parameters ​

Every request between OP and 2J servers must carry the following parameters in the querystring:

ParameterTypeRequiredDescription
mchintYesIdentifier ID assigned by 2J to the accessing OP (usually a three-digit or four-digit ID)
tsintYesTimestamp in milliseconds
signstringYesSignature, see Signature Calculation and Verification for the rules
sweepstakesstringNoSpecify the currency type: sc/gc; required for merchants with SweepStakes accounts

Amount Parameters ​

Amount parameters in interface interactions between OP and 2J servers are in li (厘) units, with data type int64.

1 yuan = 1000 li
1 fen = 10 li

Signature Calculation and Verification ​

Calculate sign for each request and include it in the URL querystring alongside mch and ts. See Common Parameters for their definitions.

Calculation Rules ​

text
sign = lowercase_hex(MD5(rawBodyBytes + UTF8(ts) + UTF8(key)))

+ means concatenating bytes in this order, without separators.

Input / outputRule
jsonbody / rawBodyBytesThe original HTTP request body bytes actually sent or received, encoded in UTF-8
tsThe decimal string of the millisecond timestamp in the URL query; use the same value for calculation and transmission
keyThe merchant's secret key, encoded in UTF-8 for calculation and kept out of the request
signThe MD5 digest as a 32-character lowercase hexadecimal string

Only the original body, ts, and key enter the digest. The URL path, mch, sweepstakes, and sign itself are not included.

The receiver calculates the signature with the received original body, the query's ts, and the corresponding merchant's key, then compares the complete result with the query's sign.

jsonbody must be the original request body

jsonbody means the original HTTP body, not a parsed JSON object or a string produced by serializing that object again.

Languages, frameworks, and serializer settings can produce different field orders, whitespace, line breaks, Unicode escapes, and number formats. For example, {"amount":1}, { "amount": 1 }, and {"amount":1.0} can represent the same business data, but their bytes and signatures differ.

Before calculating the signature, do not parse and reserialize, sort, format, minify, or trim the body. Preserve its original whitespace, line breaks, and escape sequences.

Language Examples ​

Each example has two purposes: calculate sign, then put it in the URL query and send a POST request. Prepare the original body once and use the same bytes for signing and sending, without JSON parsing or reserialization.

The full API URL https://api.example.com/open2j/c/create, mch = 108, and key = example_key are placeholders. Replace them with values for your test environment. ts is the current request's millisecond timestamp, so the signature changes when the body, ts, or key changes. Expand a language to view the code and save source files as UTF-8.

Go · Go 1.17+ / Standard library

In an HTTP service, use io.ReadAll(r.Body) to read the original bytes and handle read errors. Sign those bytes rather than JSON recreated from a struct.

go
package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// rawBody must be the exact bytes sent or received over HTTP.
func computeSign(rawBody []byte, ts, key string) string {
	h := md5.New()
	h.Write(rawBody)
	h.Write([]byte(ts))
	h.Write([]byte(key))
	return hex.EncodeToString(h.Sum(nil))
}

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

func run() error {
	rawBody := []byte(`{"op_id":"player01","user_info":{"nickname":"Alice","gender":0,"cnt":"ph","lan":"en"}}`)
	mch, key := "108", "example_key"
	ts := strconv.FormatInt(time.Now().UnixMilli(), 10)
	// Sign the original UTF-8 bytes; do not parse or reserialize the JSON.
	sign := computeSign(rawBody, ts, key)
	query := url.Values{"mch": {mch}, "ts": {ts}, "sign": {sign}}.Encode()
	endpoint := "https://api.example.com/open2j/c/create?" + query
	// Send the exact same bytes used above to compute the signature.
	req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(rawBody))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	fmt.Printf("Status: %s\n%s\n", resp.Status, body)
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("HTTP request failed with status %d", resp.StatusCode)
	}
	return nil
}
Java · JDK 11+ / Standard library

In a Servlet service, read the original bytes from request.getInputStream() rather than rebuilding the body from a DTO or Map. Compile with javac -encoding UTF-8 when source files contain non-ASCII characters.

java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;

public class SignatureExample {
    // rawBody must be the exact bytes sent or received over HTTP.
    public static String computeSign(byte[] rawBody, String ts, String key)
            throws NoSuchAlgorithmException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        md5.update(rawBody);
        md5.update(ts.getBytes(StandardCharsets.UTF_8));
        md5.update(key.getBytes(StandardCharsets.UTF_8));
        StringBuilder hex = new StringBuilder(32);
        for (byte value : md5.digest()) {
            hex.append(String.format("%02x", value & 0xff));
        }
        return hex.toString();
    }

    public static void main(String[] args) throws Exception {
        byte[] rawBody = ("{\"op_id\":\"player01\",\"user_info\":{\"nickname\":\"Alice\","
                + "\"gender\":0,\"cnt\":\"ph\",\"lan\":\"en\"}}").getBytes(StandardCharsets.UTF_8);
        String mch = "108";
        String key = "example_key";
        String ts = Long.toString(System.currentTimeMillis());
        // Sign the original UTF-8 bytes; do not parse or reserialize the JSON.
        String sign = computeSign(rawBody, ts, key);
        String query = "mch=" + URLEncoder.encode(mch, StandardCharsets.UTF_8)
                + "&ts=" + URLEncoder.encode(ts, StandardCharsets.UTF_8)
                + "&sign=" + URLEncoder.encode(sign, StandardCharsets.UTF_8);
        URI endpoint = URI.create("https://api.example.com/open2j/c/create?" + query);
        HttpRequest request = HttpRequest.newBuilder(endpoint)
                .timeout(Duration.ofSeconds(10))
                .header("Content-Type", "application/json; charset=utf-8")
                // Send the exact same bytes used above to compute the signature.
                .POST(HttpRequest.BodyPublishers.ofByteArray(rawBody))
                .build();
        HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
        HttpResponse<String> response = client.send(
                request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
        System.out.println("Status: " + response.statusCode());
        System.out.println(response.body());
        if (response.statusCode() < 200 || response.statusCode() >= 300) {
            throw new IllegalStateException("HTTP request failed with status " + response.statusCode());
        }
    }
}
Node.js · Node.js 18+ / ES Modules

Retain the original request body as a Buffer. With Express or similar frameworks, capture it before JSON parsing middleware; do not call JSON.stringify on parsed req.body.

js
import { createHash } from 'node:crypto'
import { pathToFileURL } from 'node:url'

// rawBody must be the exact Buffer sent or received over HTTP.
export function computeSign(rawBody, ts, key) {
  return createHash('md5')
    .update(rawBody)
    .update(ts, 'utf8')
    .update(key, 'utf8')
    .digest('hex')
}

async function main() {
  const rawBody = Buffer.from('{"op_id":"player01","user_info":{"nickname":"Alice","gender":0,"cnt":"ph","lan":"en"}}', 'utf8')
  const mch = '108'
  const key = 'example_key'
  const ts = Date.now().toString()
  const url = new URL('https://api.example.com/open2j/c/create')
  url.search = new URLSearchParams({ mch, ts, sign: computeSign(rawBody, ts, key) }).toString()
  const controller = new AbortController()
  const timeout = setTimeout(() => controller.abort(), 10_000)
  try {
    // Node.js 18+: send the same Buffer that was signed.
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=utf-8' },
      body: rawBody,
      signal: controller.signal,
    })
    console.log(`HTTP ${response.status}`)
    console.log(await response.text())
    if (!response.ok) process.exitCode = 1
  } finally {
    clearTimeout(timeout)
  }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch(() => {
    console.error('Request failed or exceeded the 10-second timeout.')
    process.exitCode = 1
  })
}
Python 3 · Python 3.7+ / Standard library

The signature function accepts bytes. For example, Flask's request.get_data() and Django's request.body expose the raw body. Do not call json.dumps on parsed data.

py
import hashlib
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


def compute_sign(raw_body: bytes, ts: str, key: str) -> str:
    """raw_body must be the exact bytes sent or received over HTTP."""
    md5 = hashlib.md5()
    md5.update(raw_body)
    md5.update(ts.encode("utf-8"))
    md5.update(key.encode("utf-8"))
    return md5.hexdigest()


def main() -> int:
    raw_body = '{"op_id":"player01","user_info":{"nickname":"Alice","gender":0,"cnt":"ph","lan":"en"}}'.encode("utf-8")
    mch = "108"
    key = "example_key"
    ts = str(time.time_ns() // 1_000_000)
    query = urlencode({"mch": mch, "ts": ts, "sign": compute_sign(raw_body, ts, key)})
    url = "https://api.example.com/open2j/c/create?" + query
    try:
        # Send the same bytes that were signed.
        request = Request(url, data=raw_body, method="POST",
                          headers={"Content-Type": "application/json; charset=utf-8"})
        with urlopen(request, timeout=10) as response:
            print(f"HTTP {response.status}")
            print(response.read().decode("utf-8", errors="replace"))
            return 0 if 200 <= response.status < 300 else 1
    except HTTPError as error:
        with error:
            print(f"HTTP {error.code}")
            print(error.read().decode("utf-8", errors="replace"))
        return 1
    except (URLError, OSError, ValueError):
        print("Request failed or exceeded the 10-second timeout.", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
PHP · PHP 7+ / Built-in functions

Use file_get_contents('php://input') to read the original body and handle read failures. PHP strings are byte sequences; do not pass the body through json_decode / json_encode.

php
<?php
declare(strict_types=1);

// Sign the original body bytes, followed by ts and key, with no separators.
function computeSign(string $rawBody, string $ts, string $key): string
{
    return md5($rawBody . $ts . $key);
}

$url = 'https://api.example.com/open2j/c/create';
$mch = '108';
$key = 'example_key';
$rawBody = '{"op_id":"player01","user_info":{"nickname":"Alice","gender":0,"cnt":"ph","lan":"en"}}';
$ts = sprintf('%.0f', floor(microtime(true) * 1000));
$sign = computeSign($rawBody, $ts, $key);
$query = http_build_query([
    'mch' => $mch,
    'ts' => $ts,
    'sign' => $sign,
], '', '&', PHP_QUERY_RFC3986);

$context = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Content-Type: application/json; charset=utf-8\r\n",
    'content' => $rawBody, // Send the same bytes that computeSign hashed.
    'timeout' => 10,
    'ignore_errors' => true, // Read non-2xx response bodies too.
    'follow_location' => 0,
]]);
$responseBody = @file_get_contents($url . '?' . $query, false, $context);
if ($responseBody === false) {
    fwrite(STDERR, "HTTP request or response read failed.\n");
    exit(1);
}

$status = 0;
foreach ($http_response_header as $header) {
    if (preg_match('/^HTTP\/\S+\s+(\d{3})\b/', $header, $matches)) {
        $status = (int) $matches[1];
    }
}
echo 'HTTP ', $status, PHP_EOL, $responseBody, PHP_EOL;
if ($status < 200 || $status >= 300) {
    fwrite(STDERR, "HTTP request returned a non-success status.\n");
    exit(1);
}

Troubleshooting ​

CheckWhat to compare
Original bodyCompare the actual bytes sent or received. Check for reserialization, reordered fields, extra spaces, or a trailing newline first.
EncodingUse UTF-8 for the body, ts, and key. Literal Chinese characters and their \uXXXX escape sequences have different bytes.
TimestampUse the same ts string from the query; do not generate a new timestamp when verifying or convert milliseconds to seconds.
Key and concatenationCheck the merchant key and concatenate body → ts → key, without separators or other query parameters.
Digest formatUse 32 lowercase hexadecimal characters, retaining leading zeroes; do not use uppercase, Base64, or the binary digest.

Signature Calculator and Verifier ​

The example body, ts, key, and matching sign are prefilled, so you can calculate and verify immediately. Replace them with your own original request data as needed; leave sign blank to calculate only. Calculations run in the current browser without network requests.

Paste the original body, preserving spaces and escapes. Do not parse and reserialize it. Textarea line breaks use LF; use the code examples for bodies containing CRLF.

Response Structure ​

All interface response structures contain the following fields:

json
{
    "header": {
        "code": 0, // Error code, 0 means normal, non-0 means abnormal
        "msg": "", // Error message
        "timestamp": 1709716095516
    },
}

Common Interfaces ​

1. User Registration /open2j/c/create ​

Method: POST

URI: /open2j/c/create

Description: Used to register user information on the 2J server.

Request Body:

Content-Type: application/json

json
{
    "op_id": "xxx-xxx-aaa", // OP side user's unique identifier, required
    "user_info": {
        "nickname": "player01", // OP player nickname. Note: emoji special characters not supported
        "gender": 0, // 0-unknown, 1-female, 2-male
        "cnt": "ph",  // Default country
        "lan": "en"   // Default language
    }
}

Parameter description:

ParameterTypeDescriptionNote
op_idstringUnique user identifier from OP sideRequired, Up to 64 lengths
user_infoobjectUser informationRequired
user_info.nicknamestringPlayer nickname from OPRequired
user_info.genderintPlayer gender from OPRequired
user_info.cntstringDefault countryOptional
user_info.lanstringDefault languageOptional

Response Body:

Content-Type: application/json

json
{
    "header": {
        "code": 0,
        "trace_id": "9cd0bfd22447bf607d20a3c2ac4e304a",
        "msg": "",
        "timestamp": 1710310573038
    }// Common response header
}

2. Get Game Link /open2j/c/launch ​

Method: POST

URI: /open2j/c/launch

Description: Used to get game link interface.

Request Body:

Content-Type: application/json

json
{
    "op_id": "xxx-xxx-aaa", // Required. OP side user's unique identifier
    "game_id": 65005, // Required. Game ID 
    "lang": "en-US",
    "backlink": "http://xxxxx.com?a=ss", // Return to OP address
    "device_type": 2,  // Optional. Player client type 1: h5, 2: android, 3: ios
    "device_id": "AD19939",  // Optional. Player client device unique ID
    "ret_lobby_btn": false, // Optional. Whether to show return to lobby button
    "full_screen": 1, // Optional. Controls whether to automatically enter fullscreen when using browser 1: don't enter fullscreen 2: automatically enter fullscreen, if not provided, controlled by 2j server
    "auto_create_account": false, // Optional. Whether to automatically create an account, true: automatically create an account, false: do not automatically create an account
    "keep_token": false // Optional. Whether to keep token unchanged
}

Parameter description:

ParameterTypeDescriptionNote
op_idstringUnique user identifier from OP sideRequired
game_idintGame IDRequired
langstringLanguageOptional
backlinkstringReturn URL to OPOptional
device_typeintClient device type 1: h5, 2: android, 3: iosOptional
device_idstringUnique device ID of clientOptional
ret_lobby_btnboolWhether to display return to lobby buttonOptional
full_screenintControl auto fullscreen in browser modeOptional
auto_create_accountboolWhether to automatically create an accountOptional
keep_tokenboolWhether to keep token unchangedOptional
  1. game_id detailed list can be found here
  2. lang reference: https://zh.wikipedia.org/wiki/ISO_639-1
  3. backlink: Controls the logic executed by the exit game button in the game
    • If about:blank or empty is passed, triggers code: window.close()
    • If a link is passed, triggers code: window.location.href=backlink
    • If using Iframe to open the game, you can use setting backlink parameter as script, for example: javascript:window.parent.postMessage({ type: 'exit_customer_page'}, '*'), this will trigger the parent page's onmessage event, then the parent page can handle according to the type parameter.

Response Body:

Content-Type: application/json

json
{
    "header": {
        "code": 0,
        "trace_id": "9cd0bfd22447bf607d20a3c2ac4e304a",
        "msg": "",
        "timestamp": 1710310573038
    },
    "url": "http://abc.zzz?brd=default&chn=&lang=en-US&launch=blackjack&mch=108&opid=demo10087&token=6defed39fd6a4e98add31a4e1ead5172&version=1"
}

3. Evict User /open2j/c/evict ​

Method: POST

URI: /open2j/c/evict

Description: Provided to OP for evicting users. If the player is in a game, they will be forcibly exited from the game.

Note: After being evicted, the player will receive a popup notification informing them of the eviction, but if OP did not provide a backlink parameter when calling /open2j/c/launch, the player will not be able to return to the OP side.

Request Body:

Content-Type: application/json

json
{
    "op_id": "xxx-xxx-aaa", // Required. OP side user's unique identifier
}

Response Body:

Content-Type: application/json

Note: If the player is in multiple games, the game information will be returned for the latest active game.

json
{
    "header": {
        "code": 0,
        "trace_id": "9cd0bfd22447bf607d20a3c2ac4e304a",
        "msg": "",
        "timestamp": 1710310573038
    },
    "game_info": {
        "game_id": 1006, // Game ID
        "join_at": 1720686368, // Join time
        "leave_at": 1720686368, // Leave time
    }
}

4. Detect User Gaming Status /open2j/c/detect-user-gaming ​

Method: POST

URI: /open2j/c/detect-user-gaming

Description: Used to detect user's gaming status.

Request Body:

Content-Type: application/json

json
{
    "op_id": "xxx-xxx-aaa" // Required. Unique user identifier from OP side
}

Response Body:

Content-Type: application/json

json
{
    "header": {
        "code": 0,
        "trace_id": "9cd0bfd22447bf607d20a3c2ac4e304a",
        "msg": "",
        "timestamp": 1710310573038
    },
    "is_gaming": false, // Whether the user is in game
    "game_ids": [1006] // List of game IDs
}

2J Game Inc.