# 08. Webhook
# 1 Setting
# 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. 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) - URL Validation: The callback URL must be a public
HTTPSaddress. Local addresses such aslocalhostand127.0.0.1are not supported. - Failure Message: If URL validation fails, the
messagefield will return the concrete reason directly.
# 1.1 Message Setting(POST)
# URL
https://developers.cjdropshipping.com/api2.0/v1/webhook/set
# CURL
curl --location --request POST 'https://developers.cjdropshipping.com/api2.0/v1/webhook/set' \
--header 'CJ-Access-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"product": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
},
"stock": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
},
"order": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
},
"logistics": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
},
"makeup": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
},
"privateOrder": {
"type": "ENABLE",
"callbackUrls": [
"https://your-host/api2.0/"
]
}
}'
| Parameter | Definition | Type | Required | Length | Note |
|---|---|---|---|---|---|
| product | Product Message | object | Y | 200 | Product Message Setting |
| - type | Product Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
| stock | Stock Message | object | Y | 200 | Stock Message Setting |
| - type | Stock Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
| order | Order Message | object | Y | 200 | Order Message Setting |
| - type | Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
| logistics | Logistics Message | object | Y | 200 | Logistics Message Setting |
| - type | Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
| makeup | Makeup Bill Message | object | N | 200 | Makeup bill message setting (created/canceled/paid notifications) |
| - type | Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
| privateOrder | Private Order Message | object | N | 200 | Private order (SY order / private inventory order) message setting (status-change notifications) |
| - type | Message type | string | Y | 200 | ENABLE,CANCEL |
| - callbackUrls | callback url | string[] | Y | 1 | Only one callback URL is supported, and it must be a reachable public HTTPS URL |
# Result
success
{
"code": 200,
"result": true,
"message": "Success",
"data": true,
"requestId": "97367e0f-cf3a-4c9b-acea-a36fb56f81b8",
"success": true
}
| Field | Definition | Type | Length | Note |
|---|---|---|---|---|
| code | error code | int | 20 | Reference error code |
| result | Whether or not the return is normal | boolean | 1 | |
| message | return message | string | 200 | |
| data | return data | object | interface data return | |
| requestId | requestId | string | 48 | Flag request for logging errors |
| success | whether the call succeeded | boolean | 1 | true-success, false-failed |
error
{
"code": 1607001,
"result": false,
"message": "Please do not use domain names such as localhost, 127.0.0.1",
"data": null,
"requestId": "a18c9793-7c99-42f9-970b-790eecdceba2",
"success": false
}
| Field | Definition | Type | Length | Note |
|---|---|---|---|---|
| code | error code | int | 20 | Reference error code |
| result | Whether or not the return is normal | boolean | 1 | |
| message | return message | string | 200 | |
| data | return data | object | interface data return | |
| requestId | requestId | string | 48 | Flag request for logging errors |
| success | whether the call succeeded | boolean | 1 | true-success, false-failed |
# 2 Product Subscription
# 2.1 Subscribe Products(POST)
Subscribe specific products or enable subscribe-all mode for product/variant/stock webhook notifications.
Note: Subscribing specific products and subscribing all products are mutually exclusive.
- If
productIdsis provided, only the specified products will be subscribed, and subscribe-all will be automatically disabled.- If only
subscribeAll=trueis provided (withoutproductIds), all previously subscribed specific products will be cleared.
subscribeAll Restriction
- Before July 2026, only users registered before June 2026 may subscribe to all products(
subscribeAll=true) - After July 2026,
subscribeAll=truewill no longer be available for any user; you must specify product IDs to subscribe
# URL
https://developers.cjdropshipping.com/api2.0/v1/webhook/product/subscribe
# CURL
curl --location --request POST 'https://developers.cjdropshipping.com/api2.0/v1/webhook/product/subscribe' \
--header 'CJ-Access-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"productIds": ["1952652478987366401", "1952652478987366402"],
"subscribeAll": false
}'
| Parameter | Definition | Type | Required | Length | Note |
|---|---|---|---|---|---|
| productIds | Product ID list | string[] | N | max 100 | Product IDs to subscribe. Cannot exceed subscription limit based on user level |
| subscribeAll | Subscribe all products | boolean | N | true=enable all products subscription, false=disable. Mutually exclusive with productIds |
# Result
success
{
"code": 200,
"result": true,
"message": "Success",
"data": {
"successProductIds": ["1952652478987366401"],
"failProductIds": ["1952652478987366402"],
"subscribeAll": false
},
"requestId": "97367e0f-cf3a-4c9b-acea-a36fb56f81b8",
"success": true
}
| Field | Definition | Type | Length | Note |
|---|---|---|---|---|
| code | error code | int | 20 | Reference error code |
| result | Whether or not the return is normal | boolean | 1 | |
| message | return message | string | 200 | |
| data | return data | object | ||
| - successProductIds | Successfully subscribed product IDs | string[] | ||
| - failProductIds | Failed product IDs (already subscribed, not exist, etc.) | string[] | ||
| - subscribeAll | Whether all-products subscription is enabled | boolean | ||
| requestId | requestId | string | 48 | Flag request for logging errors |
| success | whether the call succeeded | boolean | 1 | true-success, false-failed |
error
{
"code": 1606010,
"result": false,
"message": "Product webhook is not enabled",
"data": null,
"requestId": "a18c9793-7c99-42f9-970b-790eecdceba2",
"success": false
}
Error Codes:
| Code | Message |
|---|---|
| 1606010 | Product webhook is not enabled |
| 1606011 | Subscription limit exceeded |
| 1606012 | Product is not subscribable |
| 1606013 | Product subscription failed |
Subscription Limits by User Level:
| Level | Max Subscriptions |
|---|---|
| lv1 | 100 |
| lv2 | 1000 |
| lv3 | 2000 |
| lv4 | 5000 |
| lv5 | 10000 |
# 2.2 Unsubscribe Products(POST)
Remove specific products from the subscription list.
# URL
https://developers.cjdropshipping.com/api2.0/v1/webhook/product/unsubscribe
# CURL
curl --location --request POST 'https://developers.cjdropshipping.com/api2.0/v1/webhook/product/unsubscribe' \
--header 'CJ-Access-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"productIds": ["1952652478987366401", "1952652478987366402"]
}'
| Parameter | Definition | Type | Required | Length | Note |
|---|---|---|---|---|---|
| productIds | Product ID list | string[] | Y | max 100 | Product IDs to unsubscribe |
# Result
success
{
"code": 200,
"result": true,
"message": "Success",
"data": true,
"requestId": "97367e0f-cf3a-4c9b-acea-a36fb56f81b8",
"success": true
}
| Field | Definition | Type | Length | Note |
|---|---|---|---|---|
| code | error code | int | 20 | Reference error code |
| result | Whether or not the return is normal | boolean | 1 | |
| message | return message | string | 200 | |
| data | return data | boolean | 1 | true=success |
| requestId | requestId | string | 48 | Flag request for logging errors |
| success | whether the call succeeded | boolean | 1 | true-success, false-failed |
# 2.3 Query Subscribed Products(GET)
Query the list of subscribed products with pagination support.
# URL
https://developers.cjdropshipping.com/api2.0/v1/webhook/product/subscribe/list
# CURL
curl --location --request GET 'https://developers.cjdropshipping.com/api2.0/v1/webhook/product/subscribe/list?pageNum=1&pageSize=20&sku=CJJJJTJT00784&shopId=123456' \
--header 'CJ-Access-Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
| Parameter | Definition | Type | Required | Length | Note |
|---|---|---|---|---|---|
| pageNum | Page number | int | N | Default: 1, min: 1 | |
| pageSize | Page size | int | N | Default: 20, min: 1, max: 200 | |
| sku | Product SPU | string | N | 21 | Optional filter by SPU |
| productId | Product ID | string | N | 50 | Optional filter by product ID |
| shopId | Shop ID | string | Y | Shop ID for authorization |
# Result
success
{
"code": 200,
"result": true,
"message": "Success",
"data": {
"pageSize": 20,
"pageNumber": 1,
"totalRecords": 50,
"totalPages": 3,
"content": [
{
"productId": "1952652478987366401",
"sku": "CJJJJTJT00784",
"productName": "Wireless Bluetooth Headphone",
"productImage": "https://cdn.cjdropshipping.com/xxx.jpg",
"status": true,
"reason": null,
"createAt": "2026-04-01 10:30:00"
}
]
},
"requestId": "97367e0f-cf3a-4c9b-acea-a36fb56f81b8",
"success": true
}
| Field | Definition | Type | Length | Note |
|---|---|---|---|---|
| code | error code | int | 20 | Reference error code |
| result | Whether or not the return is normal | boolean | 1 | |
| message | return message | string | 200 | |
| data | return data | object | Pagination wrapper | |
| - pageSize | Items per page | int | ||
| - pageNumber | Current page | int | ||
| - totalRecords | Total item count | int | ||
| - totalPages | Total page count | int | ||
| - content | Product list | array | ||
| -- productId | Product ID | string | 50 | |
| -- sku | Product SPU | string | 21 | |
| -- productName | Product name | string | ||
| -- productImage | Product image URL | string | ||
| -- status | Subscription status | boolean | true=active, false=inactive | |
| -- reason | Inactive reason | string | 255 | e.g. "Product delisted" |
| -- createAt | Subscription time | string | Format: yyyy-MM-dd HH:mm:ss | |
| requestId | requestId | string | 48 | Flag request for logging errors |
| success | whether the call succeeded | boolean | 1 | true-success, false-failed |
# 3 Notification Rules
# 3.1 Product/Variant Notification Filtering
When a product or variant changes, the webhook notification is sent based on user's subscription:
- If user has
subscribeAllProducts=true→ notification is sent - If user has subscribed the specific product → notification is sent
- Otherwise → notification is skipped
# 3.2 Stock Notification Filtering
Stock change notifications follow the same product subscription filtering rules. Only users who subscribed the corresponding product will receive stock change notifications.
# 3.3 Auto-Close Mechanism
- Webhook success/failure counts are recorded per hour per topic
- If the success rate in each of the previous 2 complete hours is below 80% (configurable), the webhook topic will be automatically closed
- Auto-closed webhooks require manual re-activation
- Close reason is recorded in the system
# 3.4 Private Inventory Outbound Order Notification
Private inventory outbound orders reuse the Order message (ORDER) topic; once the order webhook is enabled you receive them without a separate subscription:
- A
messageType=INSERTmessage is pushed when the private inventory outbound order is created (regular orders are not pushed on INSERT); - Subsequent status/payment/shipment changes are pushed as
UPDATE, same as regular orders; - The order message
paramscarriesprivateOutboundOrder=truefor these orders,falsefor regular orders; - For the order message body fields, see Start - Webhook Order message.
# 4 Makeup Bill Message
After subscribing, the following makeup-bill changes are pushed to the registered makeup callback URL:
| messageType | Trigger | params.status |
|---|---|---|
INSERT | Makeup bill created | CREATED |
CANCEL | Makeup bill canceled | CANCELED |
PAID | Makeup bill paid (completed) | PAID |
# 4.1 Payload Example
{
"messageId": "f3c2a1d09e8b4c5da6b7c8d9e0f1a2b3",
"type": "MAKEUP",
"messageType": "PAID",
"params": {
"orderId": "BT2606061320024499900",
"relationOrderId": "SD2606060858539645300",
"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"
}
}
Order number notes:
orderIdis the makeup bill number, alwaysBT-prefixed (e.g.BT2606061320024499900), same asorderCodein the makeup list;relationOrderIdis the original CJ order being made up — its type depends on the original order (private inventory outboundSD…, dropshipping/direct, etc.) and can be used to look up the original order via the order query API.
| Field | Type | Description |
|---|---|---|
| messageId | string | Unique message ID (unchanged on retry, usable for idempotent dedup) |
| type | string | Business type, always MAKEUP |
| messageType | string | INSERT created / CANCEL canceled / PAID paid |
| params.orderId | string | Makeup bill number, BT prefix, same as orderCode in the makeup list, e.g. BT2606061320024499900 |
| params.relationOrderId | string | Original CJ order number being made up; type varies (private inventory outbound SD…, dropshipping/direct, etc.), e.g. SD2606060858539645300 |
| params.payOrderId | string | Makeup payment order number (returned after payment) |
| params.amount | number | Makeup amount in USD |
| params.reason | string | Makeup reason (English) |
| params.type | int | 1 = makeup |
| params.diffUseType | int | 0=order makeup 1=Balance Top-up 2=Repayment 3=Transfer Shipping Fee |
| params.status | string | CREATED / CANCELED / PAID |
| params.createDate | string | Create time yyyy-MM-dd HH:mm:ss |
| params.paymentDate | string | Payment time (returned when PAID) |
# 4.2 Signature Verification
The request carries a sign header: sign = Base64( HmacSHA256( secret = your openId string, message = raw JSON request body ) ). Compute HmacSHA256 over the raw body with your openId as the secret, Base64-encode it, and compare with the sign header.
# 4.3 Response Requirements
Same as other topics: return 200 OK within 3 seconds; failed pushes are retried (up to 3 times); continuous failures trigger the auto-close mechanism (see 3.3).
# 5 Private Order Message (PrivateOrder)
After subscribing the privateOrder topic, status changes of private orders (SY order / private inventory order, order number prefixed with SY) are pushed to the registered callback URL. Only SY orders are pushed (dropshipping/deposit orders are excluded).
| messageType | Trigger |
|---|---|
UPDATE | SY order status changes (e.g. awaiting payment → paid → awaiting dispatch → dispatched → completed/canceled) |
# 5.1 Payload Example
{
"messageId": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"type": "PRIVATE_ORDER",
"messageType": "UPDATE",
"params": {
"orderId": "SY2606061320024499900",
"orderNumber": "shop_order_123",
"status": "SHIPPED",
"orderType": 2,
"createDate": "2026-06-04 10:00:00",
"paymentDate": "2026-06-04 12:00:00",
"deliveryDate": "2026-06-05 09:00:00",
"completeDate": null
}
}
| Field | Type | Description |
|---|---|---|
| messageId | string | Unique message ID (unchanged on retry, usable for idempotent dedup) |
| type | string | Business type, always PRIVATE_ORDER |
| messageType | string | UPDATE - status change |
| params.orderId | string | Private order number. Number rule: fixed SY prefix + 19-digit number (snowflake id), e.g. SY2606061320024499900 |
| params.orderNumber | string | Store order number |
| params.status | string | Order status name, see 5.2 |
| params.orderType | int | Order type: 2=stock-up (private inventory order / SY order). The private-order webhook currently only pushes SY orders, so orderType is always 2 |
| params.createDate | string | Create time yyyy-MM-dd HH:mm:ss |
| params.paymentDate | string | Payment time (returned when paid) |
| params.deliveryDate | string | Dispatch (outbound) time (returned when shipped) |
| params.completeDate | string | Completion time (returned when completed) |
# 5.2 status values
| status | Description |
|---|---|
| WAIT_PAY | Awaiting payment |
| PAYMENT_INCOMING | Payment incoming |
| PAID | Paid, pending |
| WAIT_SHIPMENT | Awaiting dispatch |
| INTERCEPTING / INTERCEPT | Intercepting / intercepted |
| SHIPPED | Dispatched |
| COMPLETED | Completed |
| OVER | Closed |
| CANCELLED | Canceled |
| REFUND_COMPLETE | Dispute refund completed |
| RESEND_OVER | Dispute resend completed |
# 5.3 Signature & Response
Same as other topics: sign = Base64( HmacSHA256( secret = your openId string, message = raw JSON request body ) ); return 200 OK within 3 seconds; failed pushes are retried (up to 3 times); continuous failures trigger the auto-close mechanism (see 3.3).