# Webhook Mechanism

# Introduction

Product details including addition, deletion & modification of variants.

# Setup Procedure

  • 1)Set up message monitoring and reference.webhook
  • 2)Set up client monitoring interface, example as below(Java).
  • 3)Test monitoring.

# Webhook Configuration Requirements

# 1. Protocol Requirements

  • Supported Protocols: HTTPS
  • Encryption: TLS 1.2 or TLS 1.3 recommended for secure transmission
  • Request Method: POST
  • Content Type: Content-Type: application/json

# 2. Signature Authentication

To make sure the Webhook request really comes from CJ, please verify the signature of every incoming request.

1. Signing algorithm

  • Algorithm: HMAC-SHA256, encoded as standard Base64 (padding kept).
  • Secret: string form of your openId (e.g. 12312 -> "12312").
  • Message: the raw JSON string of the HTTP request body.
  • Output: the Base64-encoded HMAC-SHA256 bytes. CJ sends this value through the sign HTTP header.

2. About openId

  • How to get it: call the Get Access Token API; the response returns your openId.
  • How to use it: save the openId in your system. After registering the Webhook, use this openId as the HMAC secret to compute the signature for every incoming push and compare with the sign header.

3. Verification steps

  1. Read the sign value from the request header (signature issued by CJ).
  2. Read the raw bytes of the request body. Use the original body you received — do not deserialize and re-serialize, otherwise field order may differ and the signature will not match.
  3. Use openId as the secret and the body string as the message, compute HMAC-SHA256 and Base64-encode it.
  4. Compare the computed signature against the sign header as plain strings. Accept the request when they match; reject otherwise.

Security notice

The openId delivered in Webhook pushes is the same value returned by the Get Access Token API; it serves as both your account identifier and the signing secret. Never share your Webhook receiver URL, the raw push payload, or any logs/screenshots that contain the openId with any third party (integration providers, ERP plugins, third-party debugging tools, etc.). Leaking the openId allows others to forge valid signatures, impersonate CJ pushes against your endpoint, or correlate your account identity. Mask the openId before forwarding traffic for support or debugging.

4. Sample code – generating a signature (Java)

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class SignUtil {

    /**
     * Compute the signature.
     *
     * @param openId   developer openId returned by the Get Access Token API, saved in your system
     * @param bodyJson raw JSON string of the request body
     * @return Base64-encoded HMAC-SHA256 signature
     */
    public static String encrypt(Long openId, String bodyJson) throws Exception {
        String secret = openId.toString();
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
        byte[] bytes = mac.doFinal(bodyJson.getBytes());
        return Base64.getEncoder().encodeToString(bytes);
    }
}

Invocation example and expected output:

long openId = 123L;
// Request body — exactly the same JSON string (and field order) pushed by CJ.
// Note: the CJ server uses fastjson, which serializes keys in alphabetical order.
String body = "{\"messageId\":\"123111\",\"messageType\":\"INSERT\",\"params\":\"123\",\"type\":\"PRODUCT\"}";
String sign = SignUtil.encrypt(openId, body);
// Expected signature: AHxoGFMoS/4mZfJ5vFes5//Pz2QibFQhh3GlrTtnWpk=

5. Sample code – verifying a signature (Java + Spring Boot)

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WebhookController {

    /** openId returned by the Get Access Token API and saved in your system */
    private static final Long OPEN_ID = 12312L;

    @PostMapping("/webhook/cj")
    public Object receive(@RequestHeader("sign") String sign,
                          @RequestBody String body) throws Exception {
        String expected = SignUtil.encrypt(OPEN_ID, body);
        if (!expected.equals(sign)) {
            throw new RuntimeException("sign verify failed");
        }
        // signature verified — continue with business logic
        return "{\"code\":200,\"result\":\"success\",\"message\":\"ok\"}";
    }
}

6. Sample code – generating and verifying a signature (Python)

import base64
import hashlib
import hmac

def encrypt(open_id: int, body: bytes) -> str:
    """
    Compute the signature.

    :param open_id: developer openId returned by the Get Access Token API
    :param body:    raw bytes of the HTTP request body (do not deserialize and re-serialize)
    :return:        Base64-encoded HMAC-SHA256 signature
    """
    secret = str(open_id).encode("utf-8")
    digest = hmac.new(secret, body, hashlib.sha256).digest()
    return base64.b64encode(digest).decode("utf-8")


# Generation example
open_id = 123
body = b'{"messageId":"123111","messageType":"INSERT","params":"123","type":"PRODUCT"}'
print(encrypt(open_id, body))
# Expected signature: AHxoGFMoS/4mZfJ5vFes5//Pz2QibFQhh3GlrTtnWpk=

Flask-based verification example (the same idea applies to FastAPI and other frameworks — the key is to read the raw body bytes before any middleware mutates them):

from flask import Flask, request, abort, jsonify
import hmac

OPEN_ID = 12312  # saved after calling the Get Access Token API
app = Flask(__name__)


@app.post("/webhook/cj")
def receive():
    sign = request.headers.get("sign", "")
    raw_body = request.get_data()  # bytes — must be the raw body
    expected = encrypt(OPEN_ID, raw_body)
    # Use hmac.compare_digest for constant-time comparison to avoid timing attacks
    if not hmac.compare_digest(expected, sign):
        abort(401)
    return jsonify({"code": 200, "result": "success", "message": "ok"})

Notes:

  1. Always sign and verify against the raw body string that you received, not against any re-serialized object.
  2. The signing algorithm does not use API Key. The secret is the openId string itself.
  3. openId is not carried in the request header — use the openId you saved on your side to compute the signature.

# 3. Response Specifications

  • Success Status Code: 200 OK
  • Timeout: Response must be returned within 3 seconds
    (Avoid long-running or complex business logic to ensure prompt response)

# List of topics

# Product Message: PRODUCT

# Occurs when a product is created or updated.

  • Sample Payload
{
    "messageId": "ca72a4834cd14b9588e88ce206f614a0",
    "type": "PRODUCT",
    "messageType": "UPDATE",
    "params": {
        "categoryId": null,
        "categoryName": null,
        "pid": "1424608189734850560",
        "productDescription": "xxxxxx",
        "productImage": null,
        "productName": null,
        "productNameEn": null,
        "productProperty1": null,
        "productProperty2": null,
        "productProperty3": null,
        "productSellPrice": null,
        "productSku": null,
        "productStatus": null,
        "fields" : [
            "productDescription"
        ]
    }
}
Parameter Definition Type Required Length Note
messageId Message Id string Y 200 Message Id
type Data Type string Y 20 PRODUCT
messageType Message type string Y 15 INSERT、UPDATE、DELETE
params object Y 5
- categoryId category Id string Y 200
- categoryName category Name string Y 200
- pid product id string Y 200
- productDescription product description string Y 2000
- productImage product image string Y 200
- productName product name string Y 200
- productNameEn product name(english) string Y 200
- productProperty1 product property string Y 200
- productProperty2 product property string Y 200
- productProperty3 product property string Y 200
- productSellPrice product sell price double Y 20
- productSku product sku string Y 200
- productStatus product status int Y 5 status:2-Off sale, 3-On Sale
- fields fields list list Y 5

Product Status

ProductStatus Description
2 Off sale
3 On Sale

# Inbound message for Variant

{
    "messageId": "7cceede817dc47ed9748328b64353c5c",
    "type": "VARIANT",
    "messageType": "UPDATE",
    "params": {
        "vid": "1424608152007086080",
        "variantName": null,
        "variantWeight": null,
        "variantLength": null,
        "variantWidth": null,
        "variantHeight": null,
        "variantImage": null,
        "variantSku": null,
        "variantKey": null,
        "variantSellPrice": null,
        "variantStatus": null,
        "variantValue1": null,
        "variantValue2": null,
        "variantValue3": null,
        "fields" : [
            "variantLength"
        ],
    }
}
Parameter Definition Type Required Length Note
messageId Message id string Y 50 Message Id
type Data Type string Y 20 VARIANT
messageType Message Type string Y 15 INSERT、UPDATE、DELETE
params object Y
- vid variant Id string Y 50
- variantName variant name string Y 200
- variantWeight variant weight, unit:g int Y
- variantLength variant length, unit:mm int Y
- variantWidth variant width, unit:mm int Y
- variantHeight variant height, unit:mm int Y
- variantImage variant image string Y 200
- variantSku variant sku string Y 200
- variantKey variant key string Y 200
- variantSellPrice variant sell price, USD double Y
- variantStatus variant status int Y 5
- variantValue1 variant value1 string Y 100
- variantValue2 variant value2 string Y 100
- variantValue3 variant value3 string Y 100
- fields fields list list Y 5

Variant Status

variantStatus Description
0 Off sale
1 On sale

# Stock Message

{
    "messageId": "ca72a4834cd14b9588e88ce206f614a0",
    "type": "STOCK",
    "messageType": "UPDATE",
    "params": {
        "1424608152007086080": [
            {
                "vid": "1424608152007086080",
                "areaId": "2",
                "areaEn": "US Warehouse",
                "countryCode": "US",
                "storageNum": 12
            }
        ],
        "AE7DB9BC-4290-4C85-B8A6-F8957F3DB053": [
            {
                "vid": "AE7DB9BC-4290-4C85-B8A6-F8957F3DB053",
                "areaId": "2",
                "areaEn": "US Warehouse",
                "countryCode": "US",
                "storageNum": 1
            }
        ]
    }
}

# Order message

Private inventory outbound orders reuse this Order message (ORDER) topic: once the order webhook is enabled you will receive them. Unlike regular orders, a private inventory outbound order is pushed at creation time as a messageType=INSERT message, distinguishable by params.privateOutboundOrder=true.

{
    "messageId": "7cceede817dc47ed9748328b64353c5c",
    "type": "ORDER",
    "messageType": "UPDATE",
    "params": {
        "orderNumber": "api_52f268d40b8d460e82c0683955e63cc9",
        "cjOrderId": "210823100016290555",
        "orderStatus": "CREATED",
        "logisticName": "CJPacket Ordinary",
        "trackNumber": null,
        "createDate": "2021-08-23 11:31:45",
        "updateDate": "2021-08-23 11:31:45",
        "payDate": null,
        "deliveryDate": null,
        "completeDate": null,
        "privateOutboundOrder": false,
        "orderItems": [
            "vid": "1392053744945991680",
			"quantity": 1,
			"sellPrice": 0.57,
            "lineItemId": "2505170958390976500",
            "storeLineItemId": "16045188153625",
            "productionOrderStatus": 1,
            "abnormalType": [
                6,
                9
            ]
        ]
    }
}
Parameter Definition Type Required Length Note
messageId Message id string Y 50 Message Id
type Data Type string Y 20 ORDER
messageType Message Type string Y 15 INSERT、UPDATE、DELETE、ORDER_CONNNECTED: This type requires special attention:The product has been re-associated in the CJ system, and the order status has been updated from incomplete to complete. At this point, The actual CJ order id is returned in this message.
params object Y
- cjOrderId CJ order id string Y 200
- orderNum Customer order number string Y 200 Will be deprecated, please use orderNumber instead
- orderNumber Customer order number string Y 200
- orderStatus CJ order status string Y 200
- logisticName logistic name string Y 200
- trackNumber track number string Y 200
- trackingUrl tracking URL string N 200
- updateDate update date string Y 200
- createDate create date string Y 200
- payDate pay date string Y 200
- deliveryDate delivery date string Y 200
- completeDate complete date string Y 200
- privateOutboundOrder Whether a private inventory outbound order boolean true=private inventory outbound order (pushed at creation as messageType=INSERT), false=regular order
- orderItems order item list List
-- vid Variant Id string 200
-- quantity quantity int 20
-- sellPrice Sell Price BigDecimal (18,2) unit:$(USA)
-- storeLineItemId The lineItemId of your store order string 125
-- lineItemId Unique ID of the order item in CJ string 50
-- productionOrderStatus Production Status Number 1 1=Pending Order, 2=Pending Production, 3=In Production, 4=Production Completed, 5=Production Abnormality
-- abnormalType Abnormal Reason int[] 6= Image link error, 9= Production drawings don't match the renderings, 10= Missing hanging ring, 11= Mismatch between die-cutting diagram and printing diagram, 12= uneven edges, 13= letters not connected, 14= Missing order images

# Order splitting message

{
    "messageId": "7cceede817dc47ed9748328b64353c5c",
    "type": "ORDERSPLIT",
    "messageType": "UPDATE",
    "params": {
        "originalOrderId": "original order id",
        "splitOrderList": [
            {
                "createAt":1673490845706,
                "orderCode":"SD1613355441583259648-2",
                "orderStatus":300,
                "productList":[
                {
                    "sku":"CJNSSYLY01043-Claret-S",
                    "vid":"2547992D-CEE1-4BFD-99AC-9E30354F771F",
                    "quantity":1,
                    "productCode":"1613355657229205504"
                },
                {
                    "sku":"CJJSAQXF00016-Orange",
                    "vid":"A9C95BCB-D824-4AA1-A389-E86F3CCB10EF",
                    "quantity":1,
                    "productCode":"1613355657229205506"
                },
                {
                    "sku":"CJNSSYCS03214-Photo Color-XXL",
                    "vid":"E5FED43E-F9DE-483F-ADCE-8C95D3380315",
                    "quantity":1,
                    "productCode":"1613355657229205507"
                }
                ]
            },
            {
                "createAt":1673490845706,
                "orderCode":"SD1613355441583259648-1",
                "orderStatus":300,
                "productList":[
                    {
                        "sku":"CJNSSYLY01043-White-M",
                        "vid":"0550DFC6-7FF7-4662-AE7D-B4DF0E4EB24A",
                        "quantity":1,
                        "productCode":"1613355657229205505"
                    }
                ]
            }
        ],
        "orderSplitTime": "拆单时间"
    }
}
Parameter Definition Type Required Length Note
messageId Message id string Y 50 Message Id
type Data Type string Y 20 ORDERSPLIT
messageType Message Type string Y 15 INSERT、UPDATE、DELETE
params Object Y
- originalOrderId Original CJ order id string N 200
- orderSplitTime Order Split Date string N 200
- splitOrderList Order List Order[] N
- - orderCode CJ order id string N 200
- - createAt Create date string N 200
- - orderStatus Order status int N 11
- - productList Product Information List Product[] N 200
- - - productCode product code string N 200
- - - vid Variant id string N 200
- - - quantity Quantity int N 10
- - - sku Sku string N 50

# Source product creation result

{
    "messageId": "7cceede817dc47ed9748328b64353c5c",
    "type": "SOURCINGCREATE",
    "messageType": "UPDATE",
    "params": {
        "cjProductId":"0550DFC6-7FF7-4662-AE7D-B4DF0E4EB24A",
        "cjVariantId":"0550DFC6-7FF7-4662-AE7D-B4DF0E4EB24A",
        "cjVariantSku":"CJ123582565212",
        "cjSourcingId":"125522",
        "status": "completed",
        "failReason":"",
        "createDate": "2023-02-07 00:00:00"
    }
}
返回字段 字段意思 字段类型 Required 长度 备注
messageId Message id string Y 50 Message Id
type Data Type string Y 20 ORDERSPLIT
messageType Message Type string Y 15 INSERT、UPDATE、DELETE
params Object Y
- cjProductId CJ product id string N 100
- cjVariantId CJ variant id string N 100
- cjVariantSku CJ variant sku string N 50
- cjSourcingId CJ sourcing Id string N 50
- status status string N 20
- failReason fail reason string N 20
- createDate create date String N 50

# Logistics message

{
    "messageId": "7cceede817dc47ed9748328b64353c5c",
    "type": "LOGISTIC",
    "messageType": "UPDATE",
    "openId": 12312,
     "params": {
        "orderId": "210823100016290555",
        "storeOrderNumbers": ["orderNum1"],
        "logisticName": "CJPacket Ordinary",
        "trackingNumber": "number12345678",
        "trackingStatus": 12,
        "logisticsTrackEvents": "[{\"status\":12,\"activity\":\" Delivered, PO Box\",\"location\":\" NENANA,AK 99760\",\"eventTime\":\"2024-01-18 07:59:22\",\"statusDesc\":\"Delivered\",\"thirdActivity\":\"Delivered, PO Box\",\"thirdLocation\":\"NENANA,AK 99760\",\"thirdEventTime\":\"2024-01-18 07:59:22\"}]"
    }
}
Parameter Definition Type Required Length Note
messageId Message Id string Y 200 Message Id
type Data Type string Y 200 LOGISTIC
messageType Message Type string Y 15 INSERT、UPDATE、DELETE
params object Y
- orderId CJ order id string Y 200 210823100016290555
- storeOrderNumbers Store order ids string[] Y 200 ["orderNum1"]
- logisticName logistic name string Y 200 CJPacket Ordinary
- trackingNumber tracking number string Y 200 number12345678
- trackingUrl tracking URL string N 200
- trackingStatus tracking status int Y 20 0=No tracking information available at the moment, 1=Warehouse Shipped Out, 2=Forwarder Warehouse Received, 3=Forwarder Return Initiated, 4=Forwarder Shipment Dispatched, 5=First-Leg International Transit, 6=Arrived at Destination Country, 7=Customs Clearance Initiated, 8=Customs Clearance Completed, 9=Last-Mile Pickup, 10=Out For Delivery, 11=Ready For Pickup, 12=Delivered, 13=Delivery Failed / Exception, 14=Return
- logisticsTrackEvents logistics track events string Y 200 [{"status":12,"activity":" Delivered, PO Box","location":" NENANA,AK 99760","eventTime":"2024-01-18 07:59:22","statusDesc":"Delivered","thirdActivity":"Delivered, PO Box","thirdLocation":"NENANA,AK 99760","thirdEventTime":"2024-01-18 07:59:22"}]

# Makeup Bill Message: MAKEUP

# Occurs when a makeup bill is created, canceled or paid.

Pushed to the registered makeup callback URL (see webhook setting for registration).

messageType Trigger params.status
INSERT Makeup bill created CREATED
CANCEL Makeup bill canceled CANCELED
PAID Makeup bill paid (completed) PAID
  • Sample Payload
{
    "messageId": "f3c2a1d09e8b4c5da6b7c8d9e0f1a2b3",
    "type": "MAKEUP",
    "messageType": "PAID",
    "openId": 12312,
    "params": {
        "orderId": "BK260526000001",
        "relationOrderId": "ZF260526000001",
        "payOrderId": "2605260000000001",
        "amount": 12.35,
        "reason": "Postage difference",
        "type": 1,
        "diffUseType": 0,
        "status": "PAID",
        "createDate": "2026-06-04 10:00:00",
        "paymentDate": "2026-06-04 12:00:00"
    }
}
Parameter Definition Type Required Length Note
messageId Message Id string Y 200 Unchanged on retry, usable for idempotent dedup
type Data Type string Y 20 MAKEUP
messageType Message Type string Y 15 INSERT-created, CANCEL-canceled, PAID-paid
openId Open Id number Y
params object Y
- orderId Makeup bill number string Y 200 Same as orderCode in the makeup list
- relationOrderId Related CJ order number string Y 200
- payOrderId Makeup payment order number string N 200 Returned after payment
- amount Makeup amount double Y (18,2) Unit: $ (USD)
- reason Makeup reason string N 500 English
- type Bill type int Y 5 1=makeup
- diffUseType Makeup purpose int Y 5 0=order makeup, 1=Balance Top-up, 2=Repayment, 3=Transfer Shipping Fee
- status Makeup bill status string Y 20 CREATED / CANCELED / PAID
- createDate Create time string Y 50 yyyy-MM-dd HH:mm:ss
- paymentDate Payment time string N 50 Returned when PAID

Status

Status Description
CREATED Makeup bill created
CANCELED Makeup bill canceled
PAID Makeup bill paid

# Listening example

# Example

package com.cj.cn.controller;

import com.alibaba.fastjson.JSON;
import com.cj.cn.constant.callback.domain.CallbackParams;
import com.cj.cn.util.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * CJ Webhook Listening Example
 *
 * @author : kay
 */
@RestController
@RequestMapping("/webhookListener")
@Slf4j
public class TestController {

    @PostMapping("/productMessage")
    public Result productMessage(@RequestBody @Validated CallbackParams query) {
        log.info("product message:{}", JSON.toJSONString(query));
        return Result.success(Boolean.TRUE);
    }
}
package com.cj.cn.constant.callback.domain;

import lombok.Data;

/**
 * @author : kay
 */
@Data
public class CallbackParams {
    private String messageId;
    private String type;
    private Object params;
}
package com.cj.cn.constant.callback.domain;

import lombok.Getter;
import org.springframework.util.StringUtils;

/**
 * @author : kay
 */
@Getter
public enum CallbackBusinessTypeEnum {
    PRODUCT,
    VARIANT,
    STOCK;

    public static CallbackBusinessTypeEnum create(String name) {
        if (!StringUtils.isEmpty(name)) {
            for (CallbackBusinessTypeEnum typeEnum: CallbackBusinessTypeEnum.values()) {
                if (typeEnum.name().equals(name.toUpperCase())) {
                    return typeEnum;
                }
            }
        }
        return null;
    }
}