Skip to content

Developer notes

Technical reference for developers who customize Shopee Marketplace Connector.

Item Value
Add-on id kv_shopee
Function prefix fn_kv_shopee_
Language variables kv_shopee.*
CSS scope .kv-shopee
PHP namespace Tygh\Addons\KvShopee
Preset marketplace (Marketplace Hub, generated by kv new --kind integration --class marketplace)
Class Role
Client\BaseClient HTTP (cURL, TLS verification, timeouts), JSON, error mapping, masked logging
Client\Api Shopee Open Platform v2: request signing, authorization link, code exchange, per-shop token refresh under a database lock, error mapping, one thin method per endpoint
Drivers\Shopee\Driver, Mapper Hub driver (SKU lookup with search_item, add_item with image upload, stock and price, orders, fulfilment, cancel, returns) and the pure translation, unit tested with the official response samples in tests/samples/
Hub\* Channel independent marketplace layer (category and attribute mapping, price rules, stock buffer, listing builder, order import, status map, shipments, returns, errors per item)
Accounts, Secret Credentials (test and live) and the stored authorization, sealed with AES-256-GCM; one account per store owner or per vendor
Config, Access Settings, owner mode, who may see and manage what
Queue, Jobs, JobException Job queue: back-off 5 min / 15 min / 1 h / 3 h / 12 h, per-job isolation, overlap lock; handlers in Jobs::handle(), recurring work in Jobs::schedule()
Log Event log with masking (partner key, sign, tokens), filters, CSV, retention
Webhook Push signature check and event ids; the controller kv_shopee.webhook?c=<account> stores the event as a job

Every call below was read from the official Shopee Open Platform v2 documentation on 2026-11-07 (Developer Guide pages open.shopee.com/developer-guide/<id> and the API reference open.shopee.com/documents/v2/<api name>?module=<n>&type=1). The reference pages are rendered by a JavaScript application; the text was read from the same JSON service the pages use (open.shopee.com/opservice/api/v1/doc/api/?api_name=<api>&version=2). The samples used by the unit tests are the response samples of those pages; several are not valid JSON as published and were repaired structurally only (see tests/samples/README.md).

Requirement of the platform (Developer Guide, “Developer account registration” and “App management”): the seller of the connected shop, or you as the developer of the app, needs a free Shopee Open Platform developer account with an app (partner ID and partner key). The owner of this add-on does not provide one; each store owner registers their own.

Common to all calls (Developer Guide: “API calls”, id 16)

Section titled “Common to all calls (Developer Guide: “API calls”, id 16)”
Topic Official behaviour Add-on
Production hosts https://partner.shopeemobile.com (developers near Singapore), https://openplatform.shopee.cn (near mainland China), https://openplatform.shopee.com.br (near the US) Setting “API host”: global, China mainland, Brazil
Sandbox hosts https://openplatform.sandbox.test-stable.shopee.sg (all developers), https://openplatform.sandbox.test-stable.shopee.cn (mainland China developers). The API reference lists https://partner.test-stable.shopeemobile.com as test_url of every API Test mode uses the sandbox host of the Guide; the setting “Sandbox host” selects the host of the API reference instead. For Brazil the Guide names no separate sandbox API host (only the auth page open.sandbox.test-stable.shopee.com.br): the global sandbox host is used (flagged, see below)
Methods, protocol GET and POST; HTTP/JSON, FORM for file upload GET with query parameters, POST with a JSON body, multipart for upload_image
Common parameters partner_id, timestamp (valid 5 minutes), sign; shop APIs add access_token (valid 4 hours) and shop_id; all in the query As documented
Signature Base string = partner_id + api path (no host) + timestamp [+ access_token + shop_id for Shop APIs]; HMAC-SHA256 with the partner key; lower-case hex Client\Api::sign(); checked in tests/unit.php against vectors computed independently (Node crypto) from the base strings printed in the Guide
Response request_id, error (empty on success), message, warning, response Api::checkBusiness(); answers with HTTP 200 and an error are errors
Error names error_auth (invalid partner_id, access_token, permission), error_param, error_sign (Wrong sign), error_rate_limit, error_limit (daily call limit), error_server, error_inner, error_network, source_ip_undeclared, error_partner_key_expired, shop_banned, invalid_acceess_token (sic) 401 (token: one refresh and repeat), 429 and 503 (the job is retried), 403 (credentials, permission, IP: a person acts), 422 (this request is wrong)
IP allow list The Console has an “IP Address Whitelist” per app (source_ip_undeclared) The error text tells the owner to declare the server IP

Authorization and tokens (Developer Guide: “Authorization and Authentication”, id 20; API reference module Public)

Section titled “Authorization and tokens (Developer Guide: “Authorization and Authentication”, id 20; API reference module Public)”
Step Official Add-on
Authorization link Fixed URL https://open.shopee.com/auth (open.shopee.cn, open.shopee.com.br; sandbox open.sandbox.test-stable.shopee.*) with partner_id, auth_type=seller, redirect_uri, response_type=code and an optional state against CSRF (Developer Guide “Authorization and Authentication”). Older apps: legacy GET {host}/api/v2/shop/auth_partner?partner_id&timestamp&sign&redirect The open.shopee.com/auth link with redirect_uri = index.php?dispatch=kv_shopee.authorize and state = <account>.<time>.<signature> (valid one hour). Account option Authorization link: legacy builds the signed auth_partner link with the state inside redirect
Redirect Shopee redirects to the redirect URL with code (valid once, 10 minutes) and shop_id (shop account) or main_account_id (main account) kv_shopee.authorize reads code, shop_id, main_account_id
Get token POST /api/v2/auth/token/get (API v2.public.get_access_token, Public API); query partner_id, timestamp, sign (over partner_id + path + timestamp); body code, partner_id, shop_id or main_account_id; answer access_token, refresh_token, expire_in, shop_id_list (main account), merchant_id_list Stored per shop id; a main account yields one token pair per authorized shop
Refresh token POST /api/v2/auth/access_token/get (v2.public.refresh_access_token); body refresh_token, partner_id, shop_id; answer new access_token, new refresh_token, expire_in; access token 4 hours, refresh token 30 days, single use and rotating; “valid for each shop_id” Refreshed when it expires within 5 minutes, under a database lock (GET_LOCK), the new pair is stored before the call continues; one forced refresh on error_auth for the token
Validity Authorization lasts at most 365 days; the seller must authorize again afterwards The Connection page shows the refresh deadline
Shop info GET /api/v2/shop/get_shop_info: shop_name, region, status, expire_time Connection test text, shop region for currency

Products (API reference module Product, 89, and MediaSpace, 91; Developer Guide “Creating product”, id 211)

Section titled “Products (API reference module Product, 89, and MediaSpace, 91; Developer Guide “Creating product”, id 211)”
API Method and path Fields used
v2.product.get_category GET /api/v2/product/get_category request language; response category_list[]: category_id, parent_category_id, original_category_name, display_category_name, has_children
v2.product.get_attribute_tree GET /api/v2/product/get_attribute_tree request category_id_list (max 20), language; response list[].attribute_tree[]: attribute_id, mandatory, name, attribute_value_list[] (value_id, name), attribute_info.input_type (1 SINGLE_DROP_DOWN, 2 SINGLE_COMBO_BOX, 3 FREE_TEXT_FILED, 4 MULTI_DROP_DOWN, 5 MULTI_COMBO_BOX)
v2.product.get_brand_list GET /api/v2/product/get_brand_list request offset, page_size (max 100), category_id, status (1 normal), language; response brand_list[], has_next_page, next_offset, is_mandatory
v2.product.get_item_limit GET /api/v2/product/get_item_limit request category_id; response price_limit.min_limit, item_name_length_limit.max_limit, item_description_length_limit.min_limit/max_limit, item_image_count_limit.max_limit
v2.logistics.get_channel_list GET /api/v2/logistics/get_channel_list response logistics_channel_list[]: logistics_channel_id, logistics_channel_name, enabled, fee_type (SIZE_SELECTION, SIZE_INPUT, FIXED_DEFAULT_PRICE, CUSTOM_PRICE), size_list[].size_id, mask_channel_id
v2.media_space.upload_image POST /api/v2/media_space/upload_image (listed as a Public API: signed without token and shop) multipart image (JPG/JPEG/PNG, 10 MB), scene = normal for item images; response response.image_info.image_id. The Guide says URLs are not supported, so the store’s images are downloaded and uploaded
v2.product.add_item POST /api/v2/product/add_item item_name, description, original_price, weight (kg), dimension (package_length, package_width, package_height), category_id, image.image_id_list, logistic_info[] (logistic_id, enabled, is_free, size_id), attribute_list[] (attribute_id, attribute_value_list[] with value_id and original_value_name; custom values use value_id 0), seller_stock[] (stock), item_sku, item_status, condition, brand (brand_id, original_brand_name); response response.item_id
v2.product.search_item GET /api/v2/product/search_item request page_size, item_sku, item_status (repeated); response item_id_list[]
v2.product.get_item_base_info GET /api/v2/product/get_item_base_info request item_id_list (max 50); response item_list[]: item_id, item_sku, item_status (NORMAL, BANNED, UNLIST, REVIEWING, SELLER_DELETE, SHOPEE_DELETE), has_model
v2.product.get_model_list GET /api/v2/product/get_model_list request item_id; response model[]: model_id, model_sku
v2.product.update_stock POST /api/v2/product/update_stock item_id, stock_list[] (model_id 0 for an item without models, seller_stock[].stock); response failure_list[] (model_id, failed_reason), success_list[]
v2.product.update_price POST /api/v2/product/update_price item_id, price_list[] (model_id, original_price); response failure_list[], success_list[]

Price decimals: two decimals are accepted in SG, MY, BR, MX, PL (Creating product guide) and ES, AR (get_model_list description); other regions round to an integer, so the add-on rounds before sending.

Orders and logistics (module Order, 94, and Logistics, 95; Developer Guide “Order Management”, id 229)

Section titled “Orders and logistics (module Order, 94, and Logistics, 95; Developer Guide “Order Management”, id 229)”
API Method and path Fields used
v2.order.get_order_list GET /api/v2/order/get_order_list request time_range_field (update_time), time_from, time_to (at most 15 days apart), page_size (1 to 100), cursor, request_order_status_pending; response more, next_cursor, order_list[].order_sn
v2.order.get_order_detail GET /api/v2/order/get_order_detail request order_sn_list (comma separated, 1 to 50), response_optional_fields, request_order_status_pending; response order_list[]: order_sn, order_status, currency, cod, create_time, update_time, total_amount, payment_method, estimated_shipping_fee, actual_shipping_fee, message_to_seller, recipient_address (name, phone, full_address, town, district, city, state, region, zipcode), item_list[] (item_id, item_name, item_sku, model_id, model_name, model_sku, model_quantity_purchased, model_discounted_price, order_item_id), package_list[] (package_number, shipping_carrier), invoice_data.number. Fields that are not “returned by default” must be listed in response_optional_fields
v2.logistics.get_shipping_parameter GET /api/v2/logistics/get_shipping_parameter request order_sn, package_number; response info_needed.pickup / dropoff / non_integrated, pickup.address_list[] (address_id, address_flag, time_slot_list[] with pickup_time_id, flags), dropoff.branch_list[].branch_id, dropoff.slug_list[].slug
v2.logistics.ship_order POST /api/v2/logistics/ship_order order_sn, package_number, exactly one of pickup (address_id, pickup_time_id, tracking_number), dropoff (branch_id, sender_real_name, tracking_number, slug), non_integrated (tracking_number)
v2.logistics.get_tracking_number GET /api/v2/logistics/get_tracking_number request order_sn, package_number; response response.tracking_number
v2.order.cancel_order POST /api/v2/order/cancel_order order_sn, cancel_reason (OUT_OF_STOCK, CUSTOMER_REQUEST, UNDELIVERABLE_AREA (TW, MY), COD_NOT_SUPPORTED), item_list[] (item_id, model_id; required for OUT_OF_STOCK), partial_cancel_item_list[] (item_id, model_id, model_quantity)

Order status values (Developer Guide “V2.0 Data Definition”, id 31): UNPAID, PENDING, READY_TO_SHIP, PROCESSED, RETRY_SHIP, SHIPPED, TO_CONFIRM_RECEIVE, IN_CANCEL, CANCELLED, TO_RETURN, COMPLETED. Imported: everything except UNPAID and PENDING.

Returns (module Returns, 102; Developer Guide “Return Refund Management”, id 227)

Section titled “Returns (module Returns, 102; Developer Guide “Return Refund Management”, id 227)”
API Method and path Fields used
v2.returns.get_return_list GET /api/v2/returns/get_return_list request page_no, page_size, update_time_from, update_time_to (at most 15 days apart); response more, return[]: return_sn, order_sn, status (REQUESTED, ACCEPTED, CANCELLED, JUDGING, CLOSED, PROCESSING, SELLER_DISPUTE), reason, text_reason, return_solution (0 return and refund, 1 refund only), create_time, update_time, item[] (item_id, model_id, name, item_sku, variation_sku)
v2.returns.confirm POST /api/v2/returns/confirm return_sn. The Guide: “agree to the buyer’s return application, only for the Full Refund type, the buyer does not need to return the product”. Therefore only requests with return_solution 1 are offered for approval

Push (webhook) (Developer Guide “Push Mechanism notifications”, id 18, and the push reference)

Section titled “Push (webhook) (Developer Guide “Push Mechanism notifications”, id 18, and the push reference)”
Topic Official Add-on
Authorization Request header Authorization = hex HMAC-SHA256 of the callback URL, a vertical bar character and the raw body, with the partner key; the signature check is “technically optional” but recommended Verified with the callback URL as the store knows it (https URL of the Connection page, plus https and http forms of the request host and URI); a push without a valid signature is answered with 400
Answer A push counts as delivered only for a 2xx answer with an empty body within the timeout (3 seconds); retries after 300, 1800 and 10800 seconds; success rate below 70% gives warnings, below 30% disables the subscription 200 with an empty body; the event is stored as a job first
Events used code 3 (order_status_push), 4 (order_trackingno_push): data ordersn, status, tracking_no; code 1, 2, 12 (authorization pushes) Codes 3 and 4 start an import (the push only says that something changed); codes 1, 2, 12 are logged
Registration In the Console (Push Mechanism page, callback URL + Verify) or with v2.push.set_push_config The documentation page of v2.push.set_push_config needs a login: not readable, so not implemented. Set the callback URL in the Console. Without a push the add-on polls on the cron schedule

Flagged: not verifiable from the public documentation, or an interpretation

Section titled “Flagged: not verifiable from the public documentation, or an interpretation”
Item What is uncertain What the add-on does
v2.push.set_push_config The documentation page is login gated Not used; Console registration
get_attribute_tree parameter name The parameter table names category_id_list; the Java request sample in the same page uses category_ids=1234,5678 category_id_list, comma separated
Array parameters in GET calls order_sn_list is documented as comma separated; the samples for item_id_list and category_id_list use commas too; item_status is documented as repeated As documented per parameter
get_return_list page_no “Specifies the starting entry … Default is 0” while the sample uses 1 Starts at 0, stops when more is false, results are de-duplicated by return_sn
search_item by item_sku The documentation does not say whether item_sku also matches model SKUs Candidates are read with get_item_base_info; for items with models get_model_list is read and model_sku compared
Brazil sandbox API host The Guide names a sandbox auth page for Brazil but no sandbox API host Global sandbox host
Redirect URL query The Guide shows redirects to a plain URL with ?code=&shop_id= appended; it does not say how an existing query string is treated state is part of the redirect URL’s query; if Shopee drops it, use the manual code field on the Connection page (code and shop ID from the address bar)
package_number on ship_order The error texts say to send it for split orders and not for unsplit orders Sent only when the order has more than one package
Partial cancellation item_list is “required when cancel_reason is OUT_OF_STOCK”; the page does not say whether it must be combined with partial_cancel_item_list Both are sent for a partial OUT_OF_STOCK cancellation
Region to currency table No table in the documentation (orders carry currency) A fixed table in Mapper::REGIONS used for price conversion; orders use the currency Shopee sends
Rate limits error_rate_limit and error_limit exist; no numbers are published 100 ms between product calls, retries with back-off

Variations (tier variations and models: items are created without variations, existing models are matched by model_sku for stock and price), bundles and kits, videos, size charts, pre-orders, certifications, global products and Merchant APIs (cross-border merchants), SIP, FBS and Instant/Mart shops, shipping documents (air waybills: print them in Shopee Seller Centre), buyer cancellation requests (handle_buyer_cancellation), return and refund offers and disputes, escrow and payout reports, Shopee Ads, vouchers, flash sales and chat.

Table Purpose
?:kv_shopee_accounts Partner ID and key per company (key sealed), last connection check, sealed authorization (tokens per shop and shop names)
?:kv_shopee_log Log
?:kv_shopee_jobs Queue
?:kv_shopee_product_map, ?:kv_shopee_order_map Product mapping and imported orders
?:kv_shopee_category_map, ?:kv_shopee_attr_map, ?:kv_shopee_options, ?:kv_shopee_cache, ?:kv_shopee_batches, ?:kv_shopee_returns Hub: mappings, rules, remembered ids, batches and returns

PHP hooks: update_product_post, delete_product_post (mark mapped products dirty) and init_templater_post (license UI plugin). Template hooks: addons:manage / tabs_content (license card) and index:styles (admin CSS).

The base URL can be overridden for tests in config.local.php (development only):

$config['kv_shopee_base_url'] = 'http://127.0.0.1:12880';

node tests/mock-server/server.js 12880 starts a local stand-in that follows the documented signatures, parameters and answers; node tests/e2e/e2e.mjs runs the HTTP end-to-end test against it; php tests/unit.php runs the unit tests.

Admin templates live in design/backend/templates/addons/kv_shopee/. Do not edit them in a store; they are replaced on update.

Shopee is a trademark of Sea Limited and its affiliates. This add-on is made by Kavio Labs Software Limited and is not affiliated with, endorsed by or sponsored by Shopee.