Skip to content

Post-Purchase Integration Guide

Post-purchase cover is sold after an order has been paid for, by email, instead of at the checkout. The customer opens a link, sees the items they actually bought, picks how long to cover them for, and pays Loxa by card.

It is an addition to an at-checkout integration, not a replacement. A retailer can run both: post-purchase catches the customers who said no, or never saw the offer.

This guide is platform-agnostic. If you are on Shopify, our app does all of it for you and you can skip to What the customer sees.

What you build

Three things, and only the first involves us:

  1. Call POST /products/post-purchase when an order is paid, with the order's lines.
  2. Store the offer_url and expires_at against the order.
  3. Email the link to the customer.

That is the whole integration. No payment handling, no cover UI, no policy management — the link opens a Loxa-hosted page that does all of it.

your platform                          Loxa
─────────────                          ────
order is paid  ──── POST ───────────▶  price the order
               ◀─── offer_url ───────  freeze the quote, mint a token

store the link
     │
     ▼
email the customer ────── they click ─▶  offer page → payment → policy
                                         documents emailed

1. Call us when the order is paid

Send the order reference and its lines. See the API reference for the full contract.

curl -sS -X POST \
  -H "x-api-key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "order_reference": "1024",
    "currency": "GBP",
    "items": [
      {"line_id":"1","sku":"VP-SOFA-001","name":"Bergen 4 Seater Fabric Sofa","price":899.00,"quantity":1}
    ]
  }' \
  "https://api.loxacover.com/test/2026-02/products/post-purchase"

On paid, not on fulfilled

Call when the order is paid, not when it ships. Two reasons: the offer runs 30 days from that call, and if you build the email at fulfilment you want the link to already exist rather than racing to create it.

Do it out of band

Pricing an order means categorising every line, which on a SKU we have not seen before is not instant. Call it from a background job rather than inside the request that confirms the order — never make a customer wait on us to see their order confirmation.

Handle eligible: false as a normal outcome

Most orders contain nothing we cover. A 200 with eligible: false is the expected answer for those, not a failure. Send no email and move on.

response = post_to_loxa(order)

if not response["eligible"]:
    return                      # nothing insurable, nothing to send

order.loxa_offer_url = response["offer_url"]
order.loxa_offer_expires_at = response["expires_at"]
order.save()

Store offer_url exactly as returned, and expires_at alongside it.

Do not rebuild the URL

The response carries a complete URL. Reconstructing it from the token and a hostname you have hardcoded is how integrations break silently: the hostname is per-retailer, and it changes when a retailer moves to their own domain.

expires_at matters because your email is often built days after the call. Template logic cannot tell whether a token has expired, so carry the date and suppress the block once it has passed.

You send the email, not us. We never receive your customer's email address, so you remain the only party who can contact them — using the consent they already gave you.

When Any time after the order is paid and before expires_at. Around dispatch or delivery tends to work best.
Where Your own transactional or marketing platform.
Who Only customers who have consented to marketing from you. This is a marketing email.

Two ways to place it

In an existing email. Add a block to your dispatch or delivery confirmation, rendered only when the order has an offer URL and the date has not passed. Cheapest option: no extra send.

As its own email. A standalone send, timed independently. Costs one more email but leaves your transactional templates alone and can be switched off without touching them.

One link, one order

The link resolves to that order and nothing else. It is a bearer credential: anyone who has it can buy cover on that order. Treat it like a password reset link — do not put it in a public page, a shared inbox, or a URL-shortening service you do not control.

Hosting the page on your own domain

By default the offer link is on a Loxa subdomain we set up for you:

https://yourshop.portal.loxacover.com/<token>

Nothing is required from you for that. It works the moment we enable your shop.

Optionally, the page can live on your domain instead — cover.yourshop.co.uk — so the link in the email matches the domain the email came from. That tends to help both trust and deliverability, and it is the only reason to do this. The page, the payment and the policy are identical either way.

What you create

Two DNS records, and we tell you the exact values.

Record Purpose
_<hash>.cover.yourshop.co.uk CNAME → _<hash>.acm-validations.aws. Proves to the certificate authority that you own the domain
cover.yourshop.co.uk CNAME → <our-load-balancer>.eu-west-1.elb.amazonaws.com Routes the hostname to us

The validation record is permanent

The first record is not a one-time step. The certificate authority re-checks it at every renewal, roughly once a year.

If it is tidied away — months later, by someone with no idea it relates to us — renewal fails silently and your payment page stops working when the certificate expires. Nothing warns you in between.

Treat it as permanent infrastructure, and make sure whoever owns your DNS knows it is there.

How it runs

It takes two passes, because the record proving you own the domain lives in a zone we do not control.

  1. We request the certificate and send you both records.
  2. You create them. Validation usually completes within minutes of the first record resolving.
  3. We attach the certificate and route the hostname. Only now does the domain serve the page — a certificate cannot be attached before it has been validated.
  4. We allow the domain on our side and point your shop at it. This is a deliberate deploy-time step rather than a setting, so a typo cannot send offer links to a domain nobody owns.

Allow a few days end to end, most of which is waiting on the two sides to talk to each other. It is not a same-day change.

Once it is live

New offers are issued on the new hostname. Links already sitting in customers' inboxes were issued against the old one and stop working, so switch before a campaign rather than during one.

What the customer sees

The link opens a page branded to you, listing the items from that order. They choose which to cover and for how long, pay by card, and receive their documents by email. Cover starts as soon as they pay.

Items we cannot cover are listed too, marked as ineligible — so the page shows their whole order rather than a mysterious subset.

Calling twice

A quote is frozen, so a retry is safe. Call again for the same order and you get the same link, the same expiry and the same prices back — not a new offer.

That covers the cases you cannot avoid: a timeout where you never saw the response, a webhook redelivery, an order re-sync after a migration.

If the order has genuinely changed — a returned line, a corrected price — the call is refused with 409 order_details_changed rather than silently re-pricing an order the customer may already have been emailed about. Send allow_requote: true to go ahead, knowing the link already sent stops working.

Once cover has been bought the order can never be re-quoted, whatever you send.

You should not need allow_requote

If you are hitting 409 order_details_changed on orders that have not changed, something in your payload is varying between calls — a price formatted differently, a name with different whitespace. That is worth finding rather than overriding, because it means your retries are not idempotent either.

Testing

  1. Ask us to enable post-purchase for your test shop. Until then every call returns 403.
  2. Place a test order containing something insurable — furniture and larger electricals are the usual candidates.
  3. Call the endpoint and check eligible and offer_url.
  4. Open the link and buy cover using a Stripe test card (4242 4242 4242 4242).
  5. Confirm the policy documents arrive.

Expect a 403 first

The most common integration problem is not a bug. Post-purchase is off by default for every shop, and a 403 means exactly that.

Checklist

  • Called on order paid, from a background job
  • eligible: false handled as a normal outcome, not an error
  • offer_url stored exactly as returned, not rebuilt
  • expires_at stored, and the email suppresses itself after it
  • Email sent only to customers who consented to marketing
  • Retries are safe — an unchanged repeat returns the same link, and a changed order is refused rather than silently re-priced
  • No PII in order_reference