The TaskSync API.
A JSON REST API, signed outgoing webhooks and a signed event endpoint for stores. The full reference, with examples, is in the app under Settings → API & webhooks → API docs.
Authentication
- The API and webhooks are part of the Enterprise plan. Create a token in Settings → API & webhooks → New API token. Choose Read, or Read and Write, and an expiry (never, 7, 30 or 90 days, or a year).
- Send it as
Authorization: Bearer tsk_…. The token is shown once and stored only as a hash. - A token acts as the person who made it, in one company, with that person's permissions at the time of each request. Revoke it at any time.
Basics
- Base address:
https://tasksync.in/api/v1 - 120 requests a minute per token, with
X-RateLimit-Remaining; over the limit you get429andRetry-After. - Lists take
pageandper_page(up to 100) and returndata,metaandlinks. - Errors look like
{"error": {"status", "code", "message", "fields"}}. - Writes go through the same checks as the app's own screens: permissions, validation, history and notifications.
Endpoints
| Method and path | Token | What it does |
|---|---|---|
GET /me | read | You, the token's company and your permissions |
GET /projects | read | Projects you can see (filters: archived, updated_since) |
GET /projects/{id}/tasks | read | Tasks in one project |
GET /tasks, GET /tasks/{id} | read | Tasks across your projects, or one task (filters: completed, assignee, updated_since, list_id, project_id) |
POST /tasks | write | Create a task (list_id and title required) |
PATCH /tasks/{id} | write | Change a task; only the fields you send change |
POST /tasks/{id}/complete | write | Complete a task, or re-open it |
POST /tasks/{id}/comments | write | Add a comment |
GET /tickets, GET /tickets/{id}, POST /tickets | read / write | Service desk tickets (service desk add-on) |
GET /commerce/orders, GET /commerce/orders/{id} | read | Store orders and the tasks made for them (e-commerce hub add-on) |
POST /commerce/stores/{id}/events | write | Send store events with an owner or admin token instead of a signature |
Webhooks
- Owners and admins add up to 20 webhooks in Settings → API & webhooks. The address must be
https. - Events:
task.created,task.updated,task.completed,task.commented,ticket.created,ticket.status_changed, and a testping. - Headers:
X-TaskSync-Event,X-TaskSync-Delivery(the same on every retry),X-TaskSync-TimestampandX-TaskSync-Signature. - Signature:
sha256=followed by the hex HMAC-SHA256 of{timestamp}.{raw body}with the webhook's secret (whsec_…, shown once). Compare in constant time and reject old timestamps (we suggest 5 minutes). - Failed deliveries (network errors, 5xx, 408, 429) are retried up to 5 times over about 3 hours. A webhook is switched off after 20 failures in a row.
$expected = 'sha256='.hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
$valid = hash_equals($expected, $signatureHeader) && abs(time() - (int) $timestamp) <= 300;
Commerce event format
The e-commerce operations hub (add-on) receives store events at a signed endpoint. The CS-Cart add-on and the OpenCart extension use this format, and so can any store through the General API.
TaskSync commerce events (v1)
Stores send what happens to TaskSync; TaskSync keeps a light copy of orders and turns events into tasks by the company's rules. The same format is used by the TaskSync add-ons (CS-Cart) and by any custom integration ("General API").
Endpoint
POST https://<your TaskSync address>/hooks/commerce/{connection-key} (the store page shows the full address)
Content-Type: application/json
X-TaskSync-Timestamp: 1759480000 (Unix seconds, must be within 5 minutes)
X-TaskSync-Signature: sha256=<hex> (HMAC-SHA256 of "{timestamp}.{raw body}" with the connection secret)
Body: {"events": [ ... ]} — 1 to 100 events. Responses:
200 {"accepted": n, "duplicates": m} · 202 {"accepted": n, "duplicates": m, "parked": true} the store is
paused in TaskSync: the events are kept and handled when it is turned on again (treat as delivered) ·
401 bad signature / old timestamp · 404 unknown or archived connection · 413 body over 2 MB · 422 invalid body (errors per event index; order.id etc. must be a
string or number, occurred_at a Unix time or ISO 8601 date) · 429 too many requests (retry later).
Retry on 429 / 5xx / network errors with backoff (1m, 5m, 30m, 2h, 12h). Events with an id
already received are acknowledged as duplicates, so retrying is always safe.
After New secret on the store page, the previous secret is still accepted for 24 hours, so the store can be updated without losing events. Calls TaskSync makes to the store (actions, feed) are signed with the new secret straight away.
Event
| field | required | |
|---|---|---|
id |
yes | unique per store, ≤ 100 chars (e.g. order:1001:status:P:1759480000) |
type |
yes | see below |
occurred_at |
yes | Unix seconds or ISO-8601 with zone |
| one object per type | yes | order, return, product, vendor, payout, message, statuses, store |
Amounts are decimal numbers in the store currency; currency is ISO-4217. vendor is only for
marketplaces ({"id": "12", "name": "Acme"}). url is an optional admin link to the record.
Only send what is needed — customer name, e-mail, phone and shipping address at most.
| type | object |
|---|---|
store.ping |
store: {name, url, platform, version, queue} — queue is optional: {waiting, failed}, the add-on's own outbox (events not yet sent, events it gave up on); shown on the store page |
store.statuses |
statuses: [{code, label, type: "order"|"return"}] |
order.created, order.updated, order.status_changed |
order: {id, number, status, status_label, previous_status, total, currency, customer: {id, name, email, phone, country, address}, vendor, items_count, items, placed_at, updated_at, parent_id, url, shipping} |
return.requested, return.updated |
return: {id, order_id, order_number, status, status_label, amount, currency, reason, customer: {name, email}, vendor, url} |
product.pending_approval, product.updated |
product: {id, name, sku, status, stock, vendor, url} |
stock.low |
product: {id, name, sku, stock, threshold, vendor, url} |
vendor.applied, vendor.updated |
vendor: {id, name, email, status, url} |
payout.requested, payout.updated |
payout: {id, vendor, amount, currency, status, type, url} |
message.received |
message: {id, thread_id, order_id, from: "customer"|"vendor"|"admin", author, subject, excerpt, vendor, url} |
order.id, return.id … are the store's own IDs (strings). previous_status is informational:
TaskSync compares with the status it last had for the order. status is the store's status code
(e.g. CS-Cart P); send store.statuses once (and when they change) so the company can map
each status to a stage. A store's own statuses are welcome: until the company maps one, TaskSync
places it by its name (e.g. "Out for delivery" → Shipped, "RTO" → Returned).
order.shipping (optional) is the parcel, apart from the order status:
{status, label, carrier, tracking_number, tracking_url, shipped_at, delivered_at}. status is one of
not_shipped, ready, partially_shipped, shipped, in_transit, out_for_delivery, delivered,
delivery_failed, returning, returned (any other text is read by its name and kept as label).
Without it, the order status says where the parcel is (each status can be given a shipping status in
the store's settings in TaskSync). Rules can react to "Shipping status changed".
order.items (optional) lists the order's lines for the order's panel in TaskSync:
[{sku, name, qty, price, id, variant, product_id, variant_id}] (up to 100; price per unit in the
order currency; id — the store's id of the line — variant (e.g. "Red / XL"), product_id and
variant_id are optional). TaskSync shows the lines from the newest event that has them, keeps the
count (items_count) on the order and the lines themselves for the sales insights (top products and
variants) and the product pages; a line is matched by id, else by its SKU, name and variant.
order.payment (optional) is how the order was paid: {gateway, type} — gateway the payment
method's name(s) (e.g. "Cash on Delivery (COD)", "Razorpay"), type cod or prepaid (else read from
the gateway: cash on delivery / COD → cod, any other → prepaid). order.region (optional) is where
it goes, {city, state, country}, kept even when the store sends no customer details (sales by
state / city); without it the customer's address is used.
order.parent_id (optional, marketplaces) is the store's id of the order a vendor order was split from.
TaskSync shows the parts together and flags a part still waiting 48 h after another part shipped.
order.customer (optional) is who placed the order: {id, name, email, phone, country, address}.
id is the store's own customer id (registered customers; leave it out for guests), phone is best
with its country code (+91 98765 43210; without one it is read with country), and address is
the shipping address {line1, line2, city, state, postcode, country}. TaskSync keeps one customer
record per company across its stores: an order is tied to the customer with the same store
customer id, else the same e-mail (any case), else the same phone. The newest order's details win.
Nothing is kept when the store's "Keep customer name and e-mail" is off (an order is then only tied
to a customer already known by the store's customer id).
Customer details are kept while the customer has ordered in the last 3 years (store setting); after that the customer's contact details and their orders' customer fields are removed. When a customer asks for their data to be erased, it is removed from the customer record, their orders, returns and the stored events; the orders stay without it. A later order with the same e-mail or phone makes a fresh customer record (the erased one is never filled again).
Example
{"events": [{"id": "order:1001:P:1759480000", "type": "order.status_changed", "occurred_at": 1759480000,
"order": {"id": "1001", "number": "1001", "status": "P", "previous_status": "O", "status_label": "Processed",
"total": 2499.00, "currency": "INR", "customer": {"id": "5521", "name": "Asha K", "email": "asha@example.com", "phone": "+919876543210",
"country": "IN", "address": {"line1": "12 MG Road", "city": "Bengaluru", "state": "KA", "postcode": "560001", "country": "IN"}},
"items_count": 2, "placed_at": 1759470000, "updated_at": 1759480000,
"shipping": {"status": "in_transit", "carrier": "Delhivery", "tracking_number": "DLV123456789",
"tracking_url": "https://www.delhivery.com/track/package/DLV123456789", "shipped_at": 1759479000}}}]}
Signing in PHP:
$ts = time(); $body = json_encode(['events' => $events]);
$sig = 'sha256='.hash_hmac('sha256', $ts.'.'.$body, $secret);
Store actions (TaskSync → store, optional)
When a company switches it on (per store and per action), TaskSync can update the store: change an order's status, add a note, add tracking, change a return's status. TaskSync calls the store's action address — the TaskSync add-on / extension provides one; a custom integration gives its own — signed exactly like events, with the same secret:
POST {action address}
X-TaskSync-Timestamp, X-TaskSync-Signature (as above: HMAC of "{timestamp}.{raw body}")
{"actions": [ {"id": "act_42", "type": "order.set_status", "order_id": "1001", "status": "P", "comment": "Packed", "notify": false} ]}
| type | fields |
|---|---|
order.set_status |
order_id, status (store status code), comment (optional), notify |
order.add_note |
order_id, note (staff note, never shown to the customer) |
order.add_tracking |
order_id, tracking_number, carrier (optional), url (optional), notify |
return.set_status |
return_id, status (store return status code), comment (optional), notify |
notify is always false unless the person ticked "Tell the customer"; the store must then not
e-mail the customer. Answer 200 {"results": [{"id": "act_42", "ok": true, "message": "…"}]};
ok: false with a message when the action could not be done (unknown order, status not
allowed, …). Refuse a bad signature or a timestamp older than 5 minutes with 401, and an action
id already done with the earlier result (actions may be retried). The store then sends the usual
event (e.g. order.status_changed) so TaskSync's copy follows.
Catch-up feed (TaskSync → store, optional)
For stores whose own API can't list what changed (OpenCart), the add-on / extension offers a signed feed that TaskSync reads every 15 minutes:
GET {feed address}?entity=orders&since=1759480000&after_id=0&limit=200
X-TaskSync-Timestamp, X-TaskSync-Signature (HMAC of "{timestamp}.{the query string exactly as sent}")
entity is orders, returns or statuses; since is Unix seconds (changed at or after);
after_id pages within the same second. Answer with events in the format above, oldest first:
200 {"events": [ … ], "next": {"since": 1759480123, "after_id": 1042}} — next is null when
there is nothing more. Event ids should be stable for the same change (e.g.
order:1001:status:2:1759480123), so the feed and the pushed events don't make duplicates.
Platforms connected through their own API (WooCommerce, Shopify, Magento 2)
Some platforms need no add-on: on Stores → Connect a store, choose the platform, paste its API keys and click Test & connect. TaskSync checks the keys (nothing is saved when they don't work), creates the platform's own webhooks pointing at
POST https://<your TaskSync address>/hooks/commerce/{connection-key}/{platform}
signed the platform's way (WooCommerce: X-WC-Webhook-Signature, base64 HMAC-SHA256 of the raw body
with a secret made for this store), reads the store's order statuses (plugins' own included) and
translates every delivery into the events above. The catch-up reads changed orders every 15 minutes.
WooCommerce (REST API v3; key with Read/Write; https only): webhooks for order.created,
order.updated, order.deleted, product.updated, customer.created, customer.updated (customers
come in with their orders). Orders carry status and label, totals, the customer and shipping address,
the lines, the admin link (HPOS orders page or the older posts page), tracking from WooCommerce Shipment
Tracking / Advanced Shipment Tracking (_wc_shipment_tracking_items), the vendor with Dokan or WCFM;
refunds come in as returned records (history: no rules); a product at or under its low-stock threshold
sends stock.low. Updates TaskSync can send: order status, a private note, tracking (through the
tracking plugin's API, else as an order note). WooCommerce has no returns of its own, and may e-mail
the customer itself when an order's status changes (its own e-mail settings). Test & reconnect on
the store page checks the keys again and turns its webhooks back on; Remove webhooks deletes them.
Shopify (the merchant's own app; GraphQL Admin API 2026-07, or SHOPIFY_API_VERSION when set). The Admin API scopes it needs:
read_orders, write_orders, read_fulfillments, write_fulfillments,
read_merchant_managed_fulfillment_orders, write_merchant_managed_fulfillment_orders, read_customers,
read_inventory, read_products, read_returns, write_returns. Two kinds of app, chosen in the wizard:
- Dev Dashboard app (every new app since 1 January 2026; the default): in dev.shopify.com/dashboard,
in the same Shopify organization as the store, create the app, set those scopes on its version and
release it, install it on the store, and copy the Client ID and Client secret. TaskSync gets
an Admin API access token by the OAuth client credentials grant (
POST https://{shop}/admin/oauth/access_token, formgrant_type=client_credentials,client_id,client_secret→{access_token, scope, expires_in: 86399}), keeps it encrypted with the store, renews it when less than an hour is left and once more when Shopify answers 401. - Older custom app made in the store admin before 2026 (Settings → Apps and sales channels → Develop
apps; unchanged): the Admin API access token (
shpat_…, shown once) and the API secret key.
In the wizard give the shop's your-store.myshopify.com domain (not its own domain), the keys and,
optionally, the low-stock level (5 by default). Connecting subscribes the webhooks orders/create,
orders/updated, orders/cancelled, orders/fulfilled, orders/paid, refunds/create,
fulfillments/create, fulfillments/update, returns/request, returns/approve, returns/decline,
returns/close, inventory_levels/update, products/create, products/update, products/delete and
app/uninstalled (existing ones are reused, never doubled; a store connected before the products topics
existed gets them from the next catch-up — no new scope: read_products was already asked for).
The subscriptions are made through the GraphQL Admin API, so Shopify serializes them in that API version
(X-Shopify-API-Version on each delivery); ones made under an older version are moved to the current one
by the next catch-up (at most once an hour) or Test & reconnect — the new one made and the old one
removed, one per topic.
Each delivery must carry X-Shopify-Hmac-Sha256 (base64 HMAC-SHA256 of the raw body with the app's
Client secret, or the older app's API secret key) and the store's
X-Shopify-Shop-Domain; a repeated delivery is kept once.
Shopify has no custom order statuses: TaskSync's status is made from the payment and fulfilment states —
pending:unfulfilled (Awaiting payment), paid:unfulfilled (To process), paid:partial (Processing),
paid:fulfilled (Completed), pending:fulfilled (cash on delivery, Shipped), partially_refunded:…,
refunded (Returned / refunded), voided, expired, cancelled — each can be mapped to another stage on
the store page. The order number is Shopify's name (#1001); tracking and the parcel's state
(in_transit, out_for_delivery, delivered, failure …) come from the fulfilments; refunds come in as
returned records (history: no rules); Shopify returns as return records (requested → approved / declined →
closed); an item whose stock at a location drops to the low-stock level sends stock.low. The catch-up
reads orders by updated_at every 15 minutes (waits and retries when Shopify rate-limits). Updates TaskSync
can send: cancel (never a refund or restock) or mark as paid (Shopify sets every other status
itself), a staff note (appended to the order's note), tracking (fulfils the open items with it, or adds it to
the newest fulfilment), return approve / decline / close. Refunds are never made from TaskSync.
Privacy webhooks: customers/data_request tells the company's owners and admins (in the app) to export the
customer's data from their customer page; customers/redact erases the customer as Erase does;
shop/redact archives the store and removes the customer details from its orders (the orders stay).
app/uninstalled is shown on the store page: make the app again and Test & reconnect with the new token.
Magento 2 (Open Source / Adobe Commerce 2.4; REST API with an integration access token, sent as a Bearer token — on 2.4.4+ allow that in Stores → Configuration → Services → OAuth; https only). Magento has no webhooks of its own, so there are two parts:
- The TaskSync module (
TaskSync_Connector, Composer packagearena/tasksync-connector; download on the store page, README inside) pushes changes as they happen, in this format, to the store's usual address/hooks/commerce/{connection-key}, signed with the connection secret like the CS-Cart add-on (/hooks/commerce/{key}/magentotakes the same). Observers only write to its outbox table; Magento's cron sends batches every minute, with retries (1m, 5m, 30m, 2h, 12h; given up after 8). It sendsorder.created,order.status_changed(withprevious_status),order.updatedwithshippingfor shipments and tracks (partially_shippedwhile lines are left), credit memos as refunded return records (return.updated, history), Adobe Commerce returns (RMA:return.requested/return.updated),stock.low,store.ping(with its outbox) andstore.statuses— each order status withstates, the order states it belongs to (Magento only lets an order move between the statuses of its current state). With Webkul Marketplace, an order whose lines are all one seller's carries that seller asvendor. The orderidis Magento'sentity_id(numberthe increment id); event ids aremg:order:{entity_id}:{updated_at}:{hash of status, total, tracking},mg:refund:{creditmemo_id},mg:rma:{id}:{status}. - TaskSync's catch-up reads
GET /V1/orderschanged since the last run (updated_at, oldest first, 100 a page) with their shipments and credit memos every 15 minutes, giving the module's ids for the same change. Until the module sends the store's statuses, Magento's standard ones are used.
Updates TaskSync can send (through the API, never the module): a comment (the order's status kept), a status of the order's current state (On Hold / back from it / Canceled through Magento's hold, unhold and cancel calls; Complete and Closed are Magento's own), tracking (a shipment of what is left with the track, or the track on the last shipment), and a return's status on Adobe Commerce. Refunds are never made from TaskSync.
Product catalogue (E-commerce → Products)
For stores connected through their API, TaskSync keeps a copy of the product catalogue — products and
their variants (title, SKU, price, stock, image, status, storefront link). The first import starts after
Test & connect and runs in the background on the long-jobs queue, one page at a time a few seconds apart
(Shopify: 15 products a page through the GraphQL Admin API, with up to 50 variants each and the rest read
per product; WooCommerce: GET /wc/v3/products, 50 a page, with each variable product's variations;
Magento: GET /V1/products, 50 a page, each SKU its own product with its stock item). The cursor is saved
after every page, so a failed run carries on from there an hour later (commerce:catalogue, hourly, at
most 5 tries in a row); a full refresh runs once a day and marks products the store no longer has as
"removed" (never deleted). Between refreshes, Shopify's products/create|update|delete and
inventory_levels/update webhooks (the variant's stock across locations is read again on the queue) and
WooCommerce's product.created|updated keep it fresh. Read-only: nothing is changed in the store. Needs
read_products and read_inventory on Shopify (already in the list above), Read access on WooCommerce,
the Catalog resource on Magento; a refusal is noted on the store page and in Products. The low-stock alert
for a Shopify item is named from the catalogue (no extra call); on Magento a variant whose stock falls to
the store's low-stock level or below on a refresh sends stock.low.
Shoppers' self-service page (optional, no add-on change)
When the company switches it on (E-commerce → Cases → Self-service & vendors), shoppers can see their orders and raise returns, replacements, cancellations and delivery issues at
https://tasksync.in/help/{company-address}/orders
The shopper types the order number and the e-mail or mobile number on the order and gets a
6-digit code (e-mail, or WhatsApp when a template is marked for codes). Nothing in the
CS-Cart add-on or the OpenCart extension needs to change: add a link to that address on the
store's "My orders" / account page (CS-Cart: Design → Layouts or a menu item; OpenCart: an
Information page or the account module). The Cases page shows the link ready to copy.
Orders must have reached TaskSync through the events above (order.items lets shoppers pick
items; customer.phone lets them use their mobile number). For marketplace stores, link store
vendors to TaskSync vendors on the store page so that vendors decide returns in the vendor portal.
Questions about the API? Ask us · All integrations