Store Builder

The Shipping app

Connect Giao Hàng Nhanh or Giao Hàng Tiết Kiệm to price, book and track carrier waybills for your orders.

Manage → Apps → Built in → Shipping. Press Install on the card, then Manage to open its configuration.

"Connect Giao Hàng Nhanh or Giao Hàng Tiết Kiệm to price, book and track carrier waybills for your orders"

One card per carrier

The screen lists each carrier as its own expandable card — today that is Giao Hàng Nhanh (GHN) and Giao Hàng Tiết Kiệm (GHTK). Each card is split into three fixed sections — Connection, Pickup address, Webhook — and carries exactly the fields that carrier needs in Connection: GHN asks for API token and Shop ID; GHTK asks for API token and Shop code — plus a Connected / Not connected badge and a Docs link out to that carrier's own developer site.

This page covers what the two carriers share. For GHN specifically — getting an account, connecting it, and the situations only GHN has (addresses since the merger, the Hải Châu house-number quirk) — see Connecting Giao Hàng Nhanh (GHN).

Giao Hàng Tiết Kiệm (GHTK) is still being finalised on this platform — the card exists, but connecting and booking through GHTK has not yet been verified end-to-end against a real account. To get ready ahead of time, you can sign up for a GHTK test account now.

Getting a GHN token

From your GHN account (full detail, including sandbox vs. live, in Connecting Giao Hàng Nhanh (GHN)):

Field Where to find it
API token "GHN portal → Chủ cửa hàng → Xem, to find your API token."
Shop ID "GHN portal → Quản lý cửa hàng, to find your shop ID."

The path named there is GHN's own Vietnamese menu labels, quoted exactly as the card shows them rather than translated — translating them would send you looking for a menu item that does not exist under that name.

Getting a GHTK token

Still being finalised. GHTK's connection has not been verified end-to-end against a real account yet — the table below is the fields the card asks for, so you can get ready with a GHTK test account in the meantime.

Field Where to find it
API token "GHTK portal → Thông tin shop → Cấu hình API, then choose "Tạo Token"."
Shop code "Your GHTK shop code — shown on the portal under Thông tin shop → Tài khoản."

The token field is the only one that does not read back once saved — same as a payment gateway's key: once saved, the field just shows that a key is on file; typing replaces it, and leaving it blank on save keeps the old one. Shop ID and the shop code read back normally, because neither one is actually a secret.

Sandbox mode

Sandbox / testing mode — "Uses the carrier's test environment — nothing here books a real pickup." Turn it on and every quote and waybill from that card goes to the carrier's dev endpoint instead of its production one. Test here before you go live. For GHN, Test connection already confirms the token, the environment (test or live) and the Shop ID; a first sandbox waybill additionally proves that the pickup and delivery addresses are accepted by the carrier.

The Testing label next to the Connected badge is visible without opening the card, but it reflects the saved state, not the switch you just moved — it only appears after you Save with sandbox turned on. The Open the merchant portal link works differently: it is always shown, even with sandbox off, and its target follows the switch immediately, before you save — the sandbox portal while the switch is on, the live one while it is off. Sign up for a test account is the one link actually gated on the switch — it only shows while sandbox is on, and whether it goes anywhere useful depends on the carrier: GHTK has a dedicated one (khachhang-staging.ghtklab.com/web/dang-ky), GHN does not, so GHN's version of that link opens 5sao.ghn.dev directly and you create the test account there.

Pickup address

Each card keeps its own pickup address — it is not the same as your store address under Settings → Address, because this is where the carrier actually comes to collect a parcel, which can be a different place than your billing address. It needs: Pickup contact name, Pickup phone, Pickup address (street and house number), then Pickup province and Pickup ward — the same two-tier administrative list (province → ward, after the 2025 restructuring) that the storefront's own checkout form uses. The ward field stays locked until a province is chosen.

Default parcel weight sits right below — used when an order has no product carrying a weight at all (see Weight, below).

Test connection

Test connection tries exactly the values you have typed on the card, even before you press Save — it saves nothing. If the API token field is blank it uses the token already saved. That lets you check the token and Shop ID/shop code first, then fill in the pickup address, switch the carrier on and Save. For GHN the check also verifies that the Shop ID belongs to the token's account (see Connecting Giao Hàng Nhanh (GHN)).

The result is reported immediately instead of making you book a real waybill to find out. Any failure raises a toast titled "Could not connect". Two failures also show a specific sentence in the result area under the button. The carrier refuses the token — including a token from the other environment than the one the Sandbox switch is set to:

"The token or shop code is not correct — check the credentials above and try again."

The token is right but GHN's Shop ID is not on that account:

"This Shop ID does not belong to this account — check the Shop ID on the GHN portal."

Every other failure shows the raw technical message (usually in English), with the carrier's own reason if the carrier returned one — regardless of the language you administer the store in, because it is not something Store Builder translates.

Webhook URL

Each card's own Webhook section spells out the right way to wire it — the two carriers differ here, so reading your own card's copy is enough:

GHN: "Send this URL to GHN support so they can configure the webhook for your shop — if your GHN dashboard has a webhook settings field, you can paste it there instead." (detail in Connecting Giao Hàng Nhanh (GHN))

GHTK: "GHTK does not offer self-service webhook registration yet — send this URL to GHTK support and ask them to attach it to your shop."

The shared mechanism behind both: a carrier calls back into your store whenever a waybill's status changes — picked up, in transit, delivered. That address is never shown by default: Show webhook URL has to be pressed every time you need it, because the tail end of the URL IS the secret that authenticates that callback — showing it by default would mean anyone who can view the screen, even a view-only role, could read that secret. Only a role with permission to edit store settings can press Show webhook URL at all.

Send it the way your card's Webhook section says — each carrier gets its own address, because each one authenticates with that card's own secret. Skip this step and a waybill still books fine, but its delivery status never updates on its own — you would have to check the carrier's own site and update the order by hand.

Regenerate webhook URL issues a new secret for that carrier. "The old URL stops working immediately." So update the carrier's dashboard with the new URL right after clicking — every event the carrier sends to the old URL afterward, including one already in flight, is rejected.

Pairing a delivery option with a carrier

A delivery option (Settings → Delivery → Delivery options) has a Fee source field: Flat / by zone (the existing behavior) or A connected carrier. Choosing a carrier reveals a Service field — Carrier's default service, or one of that carrier's own named services (GHN: Light goods (E-commerce) only; GHTK: Road, Air, XFAST). That field is locked with the hint "Connect a carrier under Apps → Shipping to price a method through it." until the store has at least one carrier both connected and switched on.

Switching the fee source never changes the option's name on the checkout form — only how its fee gets calculated.

When the carrier does not answer in time

Every time checkout prices a carrier-backed option, the store waits at most 3 seconds for that carrier. Whether the carrier answers slower than that, answers with an error, or cannot resolve the shopper's ward to its own address codes, the fee falls back to that option's own flat/zone fee, and checkout still completes normally. A carrier's own quote of exactly 0 is treated as meaningless and discarded — that does not mean the fallback fee itself can never be zero. The fallback fee equals whatever flat/zone fee you set for that option, including a fee you set to zero. The method dialog warns you when a carrier-priced method's own fee is 0:

"This method's flat fee is 0. If the carrier cannot quote a fee, the order ships free."

Set a realistic fallback fee instead. Every fallback is logged, one line, on the server.

Free shipping above a threshold always wins outright: once an order has reached its free-shipping amount, no carrier is asked for a price at all, even on an option paired with one.

Booking a waybill from an order

Open an order: if the store has at least one carrier that is both connected and switched on (having Token/Shop ID saved is not enough by itself — Enable this carrier also has to be on), a Book a carrier shipment button appears next to the manual parcel button. Opening it shows the Book a carrier shipment dialog: "Pick a connected carrier and its service — the waybill is booked live against its API." The Carrier field is preselected to the first connected carrier already; choose a Carrier and a Service (or leave it default) and press Book shipment — the waybill goes straight to the carrier, with no preview or approval step in between.

Once booked, the order's parcel row gains a tracking number, a tracking link (donhang.ghn.vn for GHN — GHTK's own tracking domain is not yet confirmed, since GHTK is still being finalised), the Carrier fee the carrier quoted, Cash on delivery if the order is paid on delivery, and the carrier's status at that moment (usually Ready for pickup).

The weight it uses

A waybill uses the sum of every product's weight in the order (each line's weight × its quantity) — the same weight recorded on each product variant, never a number typed by hand at booking time. An order whose products carry no weight at all falls back to the Default parcel weight set on that carrier's own card.

Cash on delivery (COD)

An order paid on delivery sends the carrier a COD amount equal to what is still owed on the order — not the full order value if the buyer already paid part of it up front. An order paid through any other method sends a COD of zero.

Printing the label

Once a waybill is booked, the parcel row shows two icon-only buttons side by side (no visible text, a tooltip on hover): Print label (a printer icon) opens that carrier's own label right there — no separate login to the carrier's site. A browser that blocks the pop-up reports "Your browser blocked the label window. Allow pop-ups for this site and try again." This button stays available on any waybill that is not Cancelled or Returned.

Cancelling a waybill

A waybill that can still be cancelled with the carrier shows a Cancel shipment icon button (next to Print label) beside its row — every status except Delivered and Cancelled counts as still cancellable, including Failed and Returned, since the carrier may still be holding a parcel it has not finished with. The confirm dialog asks:

"“{tracking}” is cancelled with the carrier. This cannot be undone."

({tracking} is replaced with that waybill's own tracking number) — confirming calls the carrier's own cancel endpoint directly, moves the waybill to Cancelled and the order back to Unfulfilled.

What a carrier status does to the order

An order carries two separate status fields: Order status (Pending / Confirmed / Shipped / Delivered / Cancelled) and Fulfillment (Unfulfilled / Partial / Fulfilled) — the second one says whether the parcel has left the warehouse, not whether the buyer has paid. Every time a carrier reports a new waybill status over its webhook, only these two fields change, and only at exactly four points:

Waybill status (badge) What the order becomes
Just booked (Ready for pickup) Fulfillment → Fulfilled
In transit Order status → Shipped
Delivered Order status → Delivered (Fulfillment stays Fulfilled)
Cancelled Fulfillment → Unfulfilled
Failed / Returned The waybill changes only — the order is left as-is for you to decide the next step

An order whose Order status is already Delivered or Cancelled is never pulled backward by a late event. And if you cancel a waybill and book a new one in its place, the old waybill can still report a late event of its own — but since it is no longer the order's active shipment, that event has no path left to the order at all.

A parcel recorded manually (typed carrier name, not booked through a connected carrier) keeps its old behavior unchanged: no carrier ever reports a status for it, so nothing moves Fulfillment automatically — you still update it by hand, exactly as before.

Known limits, right now

A few things worth knowing up front, so you are not hunting for a feature that does not exist yet:

  • GHN only offers its Light goods (E-commerce) service. GHN's heavy-goods service prices and books by a parcel's real weight and dimensions, and this platform does not collect per-product dimensions yet — it will be added once it does.
  • The parcel size sent to a carrier is a fixed, standard box, never derived from the real dimensions of what is in the order — only the weight is a real number.
  • The first quote for a brand-new ward may fall back to the flat fee. The first time an address is quoted, the store has to resolve that ward's own carrier-side code; if that lookup does not finish inside the 3 second budget, that one quote falls back to the option's flat/zone fee. Later quotes for the same ward reuse the result already resolved, so they almost always get the carrier's real price.

Disconnecting a carrier

Disconnect on a connected card deletes its token, Shop ID/shop code and saved pickup address outright: "Its credentials and pickup address are forgotten. Methods priced through it fall back to their flat fee." Every delivery option paired with that carrier immediately prices its own flat/zone fee instead.

Because this is a deletion, not just a switch flip, it also affects waybills already booked: Print label and Cancel shipment on that carrier's older waybills stop working (there is no connection left to call the carrier's API with), and status events the carrier sends to the old webhook URL are rejected too — those waybills stop updating on their own. Reconnecting later also issues a new webhook URL, which has to be registered with the carrier again from scratch. If a parcel is still in transit, turn Enable this carrier off on that card instead of pressing Disconnect — a carrier that is switched off but not deleted still supports Print label, Cancel shipment, and still receives webhook status updates normally; it just stops being offered for new waybills.

For that reason Disconnect is refused while that carrier still has waybills in transit (not delivered, cancelled or returned). The confirmation dialog stays open and gives the reason with the count:

"{carrier} can't be disconnected yet. Shipments still in transit: {count}. Switch {carrier} off instead — shipments in transit can still be printed, cancelled and tracked."

A refusal that carries no count uses the shorter sentence: "{carrier} can't be disconnected yet. Switch {carrier} off instead — …". If the in-transit check itself fails, Disconnect is refused too: the message is "Could not disconnect this carrier" and nothing is deleted. With nothing in transit, Disconnect works as described above and deletes the saved credentials and pickup address.

Removing the whole Shipping app is blocked while any carrier is still switched on (Enable this carrier) — the same reason the Payments app blocks removal while a gateway is still on: an app that is "removed" while one of its connections is still live is not a state the platform allows. Trying to remove it while a carrier is on reports:

"Switch every shipping carrier off first — orders are still being booked against one."

You only need to switch off every card — pressing Disconnect is not required. Once all are off, the app can be removed.

Updated 07/10/2026