Add shipping insurance
Quote package protection with the SDK, then register the policy with one server-to-server call.
Shipping insurance on the custom path works like warranties do: the browser SDK quotes and displays the offer, and your server makes the call that creates the policy when the order is placed. For what shipping insurance covers and how the premium works, see Shipping Insurance.
Four pieces make it work:
- Quote and show the offer (browser, SDK). Fetch a quote for the cart subtotal and render the Package Protection widget. When the customer opts in, you add the premium to your own cart as a line item.
- Register the policy (server, REST). When the order is placed, POST it — including the quote's
offerId— toPOST /api/v2/shipping-insurance/orders. This is what creates the policy. - Report the shipment (server, REST). Send the tracking number when the order ships.
- Report a cancellation (server, REST) if the order is cancelled.
Registering the policy is what creates itCharging the customer the premium does not insure anything by itself. Treat a successful
POST /api/v2/shipping-insurance/ordersresponse as the definition of "the policy exists" — if the call fails, retry it or flag the order for follow-up.
Prerequisites
- The SDK is loaded and initialized with your public token. See Initialize the SDK.
- Shipping insurance is enabled on your Mulberry account by the Mulberry team, including your customer rate. Contact your account manager if you're not sure.
- Your server has your API token for the registration and lifecycle calls — the same credential split as Register the sale: public token in the browser, API token server-side only.
Quote and show the offer
After mulberry.core.initCompleted resolves, the SDK exposes mulberry.shippingInsurance:
await window.mulberry.core.init({ publicToken: "<your-public-token>" });
if (window.mulberry.shippingInsurance.isEnabled()) {
const offer = await window.mulberry.shippingInsurance.getOffer({
cartSubtotal: "100.00",
});
const widget = await window.mulberry.shippingInsurance.render({
offer,
selector: ".shipping-insurance-slot",
placement: "cart",
onToggle: (selected, offer) => {
// Add or remove the premium as a line item in YOUR cart.
// Mulberry never touches your cart — this is your code.
},
});
}isEnabled()—trueonce init has completed, shipping insurance is enabled on your account, and the shopper's location is eligible. Check it before quoting.getOffer({ cartSubtotal })— quotescartSubtotal × your customer rateand returns{ offerId, premium, cartSubtotal }(all strings). Pass the product subtotal only — no shipping, tax, or the premium itself.render({ offer, selector, placement, onToggle, defaultSelected })— renders the Package Protection card (title, description, terms link, checkbox) into yourselectorelement, inheriting its font and text color.placementpicks between two copy slots,"cart"(default) and"shipping_options"— the names describe where a checkout typically offers protection, but they're just independently configured wording variants; use the slot whose copy Mulberry set up for where your card sits.onToggle(selected, offer)fires on every checkbox change — this is where you add or remove the premium line in your cart.defaultSelected(defaultfalse) renders the checkbox already selected and fires nothing. Pass it when protection is already in the shopper's cart, or when you default it on — in that case add the premium line yourself, sinceonToggleonly fires on a shopper's change. If the card re-mounts mid-session it comes back as the shopper last left it.- The returned widget instance has
update(offer)anddestroy().
Don't pre-select where pre-ticked paid add-ons are prohibited
defaultSelectedcharges the shopper for an add-on they never actively chose. A pre-ticked box is not valid consent to an extra payment in the EU or the UK, and other markets have comparable rules. If you sell into the EU or the UK, leavedefaultSelectedoff, and clear it with your own legal team before turning it on anywhere else.
You should now see the Package Protection card inside your selector element — the title with the premium, a short description with a terms link, and a checkbox. If nothing rendered, check the browser console: every failure logs its reason there.
When the cart changes, quote again and refresh the widget:
const fresh = await window.mulberry.shippingInsurance.getOffer({ cartSubtotal: newSubtotal });
widget.update(fresh);Register with the latest offerId. A quote is valid for 24 hours and each offerId can create one policy; superseded quotes simply go unused.
On failure, the SDK returnsnull
getOfferandrendernever throw — they log the reason to the browser console and returnnull(SDK not initialized, shipping insurance not enabled, invalid subtotal, selector not found). Code defensively: ifgetOfferreturnsnull, don't show the offer.
Register the policy
When the order is placed, your server makes one call. Make it for every order if you buy protection on shoppers' behalf (see Retailer-paid protection below); otherwise, for the orders where the customer opted in:
POST https://partner.getmulberry.com/api/v2/shipping-insurance/orders
Authorization: Bearer <your-api-token>
Content-Type: application/json
(Use https://partner-staging.getmulberry.com against staging.)
| Field | Type | Required | Notes |
|---|---|---|---|
external_order_id | string | Yes | Your order id. Also the idempotency key — retries with the same id are safe. |
offer_id | string | Unless retailer_paid | The offerId from the quote the customer accepted. Omit when retailer_paid. |
premium | decimal | Unless retailer_paid | What the customer paid for protection. Must match the quote exactly. Omit when retailer_paid. |
order_items_subtotal | decimal | Yes | Product subtotal including the shipping insurance premium, if the customer paid one. |
order_shipping | decimal | Yes | Shipping charged. Send 0 if none. |
order_tax | decimal | Yes | Tax charged. Send 0 if none. |
retailer_paid | boolean | No | Default false. Send true when you are buying protection on the shopper's behalf — see below. Available once Mulberry enables it on your account. |
customer | object | When retailer_paid | email (required within the object), first_name, last_name, phone, billing address fields. Strongly recommended otherwise — the customer receives the confirmation email and is who files claims. |
order_items_subtotalincludes the shipping insurance premiumProducts
$100.00+ shipping insurance premium$2.00→ send102.00. Mulberry subtracts the premium before calculating coverage, so leaving it out under-insures the order by that amount.
Responses
201 Created—{"policy_uuid": "...", "insured_value": "111.11"}. The policy exists.200 OK— replay of anexternal_order_idyou already registered; returns the originalpolicy_uuid. Retries are safe.
| Status | Body | Meaning |
|---|---|---|
400 | {"error": "Invalid payload", "errors": {...}} | Missing or malformed fields; errors maps each field to its problem. |
400 | {"error": "Invalid offer token: expired"} | The quote is older than 24 hours — quote again next time; this order needs Mulberry support. |
400 | {"error": "premium does not match the quoted offer"} | The premium you sent isn't the one this offer_id quoted. |
400 | {"error": "Shipping insurance not configured for this retailer"} | Not enabled on your account. |
400 | {"error": "Retailer-paid registration is not enabled for this retailer"} | retailer_paid isn't switched on for your account. |
409 | {"error": "Offer token already used"} | This offer_id already created a policy for a different order. Each quote registers once. |
409 | {"error": "Order already registered with a different payer"} | This external_order_id was first registered the other way — customer-paid versus retailer-paid. |
401 | — | Wrong or missing token. This endpoint takes the API token, not the public token. |
502 | {"error": "Customer could not be recorded"} | Retailer-paid only: Mulberry couldn't save the customer record, so nothing was recorded. Retry. |
The insured value is derived by Mulberry, not sent by you. On a customer-paid registration, from your breakdown and the signed quote: the products (net of premium) are covered up to the quoted subtotal, and shipping + tax on top, also capped at the quoted subtotal. Example: a $100.00 quote at a 2% rate → premium $2.00; registering order_items_subtotal 102.00, order_shipping 5.00, order_tax 6.11 insures $111.11. Because coverage is capped at the quoted basis, always register with a fresh quote for the final cart — a stale, smaller quote caps what the customer is covered for.
All amounts are plain decimal strings in your account's configured currency; there is no currency field.
Retailer-paid protection
If you buy protection on behalf of shoppers who decline it, register those orders with retailer_paid: true and no offer_id or premium. Mulberry records a $0 customer premium and bills your account the wholesale rate on the insured value, exactly as for a customer-paid policy. The insured value is the product subtotal plus shipping and tax, with shipping and tax capped at the product subtotal.
Registering the order sends these shoppers nothing — no order confirmation and no account invitation. The shopper still has to be able to file a claim, which is why customer with an email is required on these registrations. When a shopper needs to claim, send them to https://explore.getmulberry.com/activate to activate an account with their order email — Activate, not Sign in: signing in before activating returns an error and mails the activation link anyway. That activation link is the only email they receive from Mulberry until they file a claim. Once the shipment has been reported, they can file a claim; Mulberry confirms it by email and handles the claim with them from there.
Register every order — the shopper's choice only changes whether you send retailer_paid or a quote — and report every shipment: a policy cannot be claimed against until its shipment is recorded. This option is switched on per account by Mulberry; ask your account manager.
Report the shipment
When the order ships, send the tracking so the shipment is registered with the insurer. Do this for every insured order, retailer-paid ones included — a claim can only be filed against a recorded shipment:
POST https://partner.getmulberry.com/api/v2/shipping-insurance/shipments
Authorization: Bearer <your-api-token>
| Field | Required | Notes |
|---|---|---|
policy_uuid or external_order_id | One of the two | Identifies the policy. |
tracking_number | Yes | |
carrier | Yes | Carrier name, e.g. "UPS". |
shipment_date | Yes | Any common date format. |
destination_address | Recommended | address1, city, state, postal_code, country. Registered with the insurer as the shipment's destination — omit it and the shipment is on file with no address. |
customer | Recommended | first_name, last_name, email, full_name. Registered with the insurer as the insured party. |
origin_address and shipping_cost are also accepted. 200 returns {"status": "Recorded", ...} with the insurer's shipment id. Retrying is safe: re-sending an already-recorded shipment replays 200 with the same stored result (if a claim has since been filed, status reads "Claimed").
One policy covers one package: sending a different tracking_number for an already-recorded policy returns 409. When an order ships in more than one package:
- Report the first package's tracking number against the order.
- Register each additional package as its own retailer-paid order: give it a new
external_order_id(for order1001, use1001-2,1001-3, …) and send only that package's items, shipping and tax. - Report each additional package's tracking number against its new
external_order_id.
502 comes in two kinds, and only one is worth repeating. {"error": "Could not reach Shipsurance"} means nothing was registered, so send the shipment again. {"error": "Shipsurance API error", "reason_code": "..."} means the insurer refused the request as sent, and resending it gets the same refusal.
A 409 whose error begins "A previous shipment for this policy was sent to Shipsurance and its outcome is unknown" is neither: the request reached the insurer and the reply was lost, so the policy may or may not be covered. Sending it again could insure the order twice, so we hold the policy back and reconcile it with the insurer ourselves. Don't retry, and expect us to contact you if anything is needed.
Report a cancellation
If the order is cancelled, void the policy:
POST https://partner.getmulberry.com/api/v2/shipping-insurance/cancellations
Authorization: Bearer <your-api-token>
Send policy_uuid or external_order_id, plus an optional reason. 200 returns {"status": "Voided"} — including when the policy is already voided, so this call is safe to repeat.
Cancel whether or not the order has shipped. Before the shipment is reported, the policy is voided on Mulberry's side alone; after, Mulberry also voids the shipment with the insurer.
The 200 means the policy is voided on our side. When a shipment was registered, cancelling it with the insurer happens straight after and is retried until the insurer confirms it; if the insurer refuses, we resolve it ourselves. Either way the policy can read Voided before the insurer's records catch up.
Two 409s are possible, and neither should be retried. A policy with a claim against it can't be voided and returns Cannot void a policy in Claimed state. An error beginning "This policy is being confirmed with the insurer" means an outcome is still outstanding — your cancellation is recorded and applied as soon as that clears.
Next
Test the full loop — quote, opt in, register, ship — against staging before going live: Test and go live.
Updated 13 days ago
