Migrate from the Legacy API

Move an existing Teak integration to the TeakOS Widget and Order Submit.

This guide is for partners already live with Teak's Legacy API integration: the tg widget and POST /orders. It maps each part of your current integration to its replacement in TeakOS integration guide, in the order you'd change them.

Your Legacy API integration keeps working while you migrate. Both flows can run on your account at the same time, so you can switch over gradually and switch back if you need to. The Legacy API documentation is still available: choose Legacy API in the version menu.

What changes

The main change is where the order's details live. In the Legacy API, the widget gave you a quote token and your server sent the full order, items and all, to Teak. Now the widget saves the quote and the customer's choice to Teak as it goes, in a session. Your server only sends the session ID, your order number, and the customer.

Legacy APITeakOS
Widget scriptclient-widget.min.js?v=4teak.js
Globaltgteak
Where the widget renders<div id="tg-placeholder"><teak-widget>
get('token') returnsA quote token (JWT)The session ID
Cart changestg('update', ...)teak('configure', ...) again
API base URLhttps://api.protecht.com/api/v2https://api.helloteak.com/v1, or https://api.sandbox.helloteak.com/v1 in sandbox. The old base URL keeps working.
Submitting an orderPOST /orders, with items, prices, and event detailsPOST /orders/{session_id}/submit, with no items or prices
Submit response201 with the order and its policies202, and the order is created a few seconds later
Policy numbersIn the submit responseIn the Contract Issued webhook
Customer linksOrder metadataRetrieve order details
After checkoutNothingteak('clearSession')

What stays the same

  • Your keys. The same public and secret keys work for both flows.
  • Authentication. Get an access token from POST /auth/token as before. It still expires after 5 minutes. Send it as Authorization: Bearer <token>; JWT <token> also still works.
  • Billing. Teak still bills you for issued protection on your regular invoice, and payment is still { "type": "invoice" }.
  • One contract per item. Each item the customer protects still gets its own contract.
  • Webhooks. Payloads, event names, and signatures are unchanged. order_number in webhooks is the order number you submit.
  • Void and cancel. Your existing Void a contract and Cancel a contract calls work for orders from either flow.
  • Client onboarding. Onboard a client is unchanged.

Before you start

  1. Ask your Teak integration contact to enable TeakOS in sandbox. Until it's enabled, Submit returns 403.
  2. Subscribe to Contract Issued in the sandbox Teak Dashboard, if you haven't already. Submit no longer returns policy numbers, so this webhook is how you learn what was issued.
  3. Build the new flow in sandbox alongside your Legacy API checkout, and follow Roll it out to switch over.

1. Replace the loader script

Replace the Legacy API loader in your page's <head> with the TeakOS Widget loader. The shape is the same; the global is teak instead of tg, and the script is loaded as a module.

<script>
(function(w,d,c,n,s,p){
  w[n] = w[n] || function () { (w[n].q = w[n].q || []).push(arguments) }, w[n].l = +new Date();
  s = d.createElement(c), p = d.getElementsByTagName(c)[0];
  s.onerror = function () { /* ... */ };
  s.async = s.src = 'https://icw.protecht.com/client-widget.min.js?v=4';
  p.parentNode.insertBefore(s, p);
})(window,document,'script','tg');
</script>
<script>
  (function (w, d, c, n, s, p) {
    w[n] = w[n] || function () { (w[n].q = w[n].q || []).push(arguments); };
    w[n].l = +new Date();
    s = d.createElement(c);
    p = d.getElementsByTagName(c)[0];
    s.async = true;
    s.type = 'module';
    s.src = 'https://icw.helloteak.com/teak.js';
    s.onerror = function () { /* ... */ };
    p.parentNode.insertBefore(s, p);
  })(window, document, 'script', 'teak');
</script>
Don't load both widgets on the same page

They share storage in the customer's browser, and an error in the new widget removes the Legacy API widget too.

The new loader loads from icw.helloteak.com instead of icw.protecht.com. If your site sets a Content Security Policy, add the new host: see Content Security Policy.

2. Replace the placeholder

Replace the placeholder <div> with a widget element, in the same place in your checkout:

<div id="tg-placeholder"></div>
<teak-widget></teak-widget>

For one offer per cart line, see Widget elements.

3. Update configure

Rename tg to teak and update your items. The TeakOS Widget needs more about each item than the Legacy API did, and it reads event dates differently:

Legacy APITeakOS
cost, with a . or , decimal separatorcost, a number or a numeric string with a . decimal separator. "123,99" isn't a valid cost, and the widget doesn't render.
name: not usedname: required
referenceNumber on each item: optionalreferenceNumber: required. It comes back in webhooks as reference_number.
event.location: not usedevent.location: required
Product types: not usedproductTypes on each item, or a widget element id: required. Use your inventory system's labels, mapped by Teak during migration. See Product types.
event.startDate and event.startTime, in UTCevent.startDatetime, an ISO 8601 date-time
event.endDate and event.endTime, in UTCevent.endDatetime, an ISO 8601 date-time
referenceNumber at the top level: requiredNot needed. Your order number goes to Submit instead.
clearSelectionNot supported. Send teak('clearSession') to make the customer choose again.

currency, locale, persist, sandbox, and the callbacks are unchanged.

Old event fields are silently ignored

If you keep sending startDate and startTime, the widget still renders, but Teak has no event date for the item. Combine them into startDatetime.

tg('configure', {
  apiKey: 'pk_...',
  sandbox: true,
  referenceNumber: 'CART-5521',
  items: [{
    cost: '120,00',
    referenceNumber: 'ITEM-1',
    event: {
      name: 'Summer Fest',
      startDate: '2026-07-18',
      startTime: '23:00:00',
    },
  }],
  optInCb: function () { /* ... */ },
  optOutCb: function () { /* ... */ },
});
teak('configure', {
  apiKey: 'pk_...',
  sandbox: true,
  items: [{
    name: 'Summer Fest - General Admission',
    referenceNumber: 'ITEM-1',
    productTypes: ['general-admission'],
    cost: '120.00',
    event: {
      name: 'Summer Fest',
      location: 'Austin, TX',
      startDatetime: '2026-07-18T23:00:00Z',
    },
  }],
  optInCb: (context) => { /* ... */ },
  optOutCb: (context) => { /* ... */ },
});

Your Legacy API dates and times were already in UTC, so you can join them with a Z:

const startDatetime = `${startDate}T${startTime}Z`;   // "2026-07-18T23:00:00Z"

A missing name, referenceNumber, or event.location stops the widget from rendering. See Configuration options for every field.

4. Update commands, methods, and callbacks

Legacy APITeakOS
tg('configure', options)teak('configure', options)
tg('update', { items })teak('configure', options) again, with the full list of items
tg.get('token')teak.get('token'), which now returns the session ID
tg.get('quote')teak.get('quote')
tg.isProtected()teak.isProtected()
tg.isFormComplete()teak.isFormComplete()
tg.removePersistedSession()teak('clearSession')
tg.clear()No equivalent. Send configure again to change the cart, and clearSession after checkout.
tg.set('optInCb', fn)Pass the new callback in your next configure.
tg.focus()No equivalent. Call scrollIntoView() on the widget element.
tg.get('links')After checkout, get the customer's links from Retrieve order details.
tg.get('premiumHash'), tg.get('quoteHash')Not needed. Teak issues the price saved in the session, so there's no quote to validate.
The update command is silently ignored

tg('update') has no effect in the TeakOS Widget, and no error is reported. Search your code for update and replace each call with configure.

The callbacks keep their names: loadedCb, optInCb, optOutCb, updatedCb, and onErrorCb. Two things change:

  • Callbacks now receive a context object with isProtected and isFormComplete. See Context.
  • onErrorCb receives the session ID, not an error message. When the widget fails, it also calls optOutCb with no context, so write optOutCb to work without one.

You can also simplify your callbacks. In the Legacy API, the recommended callbacks sent the quote token to your server on every change. Now the widget saves the customer's choice to Teak itself, so your callbacks only need to update your order summary. Your server gets the session ID once, at checkout. See Handle the customer's choice.

5. Replace the order request

On your server, replace POST /orders with Submit an order. The session ID goes in the path, and the session already holds the items, prices, currency, and event details, so you don't send them.

Legacy API POST /ordersPOST /orders/{session_id}/submit
tokenThe session ID, in the path
order_numberreference_id
currencyNot sent: taken from the session
itemsNot sent: taken from the session
eventNot sent: set on each item in the widget
customercustomer, unchanged. first_name, last_name, and email are required.
billing_address.address1billing_address.address_1
billing_address.address2billing_address.address_2
billing_address.zip_codebilling_address.postal_code
billing_address.city, state, countryUnchanged
payment{ "type": "invoice" }, unchanged
curl -X POST https://api.sandbox.protecht.com/api/v2/orders \
  -H 'Authorization: JWT <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<quote token>",
    "order_number": "ORD-1001",
    "currency": "USD",
    "customer": {"first_name": "Ada", "last_name": "Lovelace", "email": "[email protected]"},
    "billing_address": {"address1": "100 Congress Ave", "city": "Austin", "state": "TX", "country": "US", "zip_code": "78701"},
    "items": [{"name": "General Admission", "reference_number": "ITEM-1", "cost": "120.00",
               "event": {"name": "Summer Fest", "start_date": "2026-07-18", "start_time": "23:00"}}],
    "payment": {"type": "invoice"}
  }'
curl -X POST https://api.sandbox.helloteak.com/v1/orders/<session ID>/submit \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "reference_id": "ORD-1001",
    "customer": {"first_name": "Ada", "last_name": "Lovelace", "email": "[email protected]"},
    "billing_address": {"address_1": "100 Congress Ave", "city": "Austin", "state": "TX", "country": "US", "postal_code": "78701"},
    "payment": {"type": "invoice"}
  }'

As in the Legacy API, submit only after your own order is confirmed, and submit every order, including ones where the customer opted out.

You don't need the quote decode endpoint

It doesn't accept session IDs. The session holds the quote the customer saw, and that's the price Teak issues.

Handle the new responses

Legacy APITeakOS
201 with the order and its policies202 Accepted. Teak creates the order a few seconds later.
204 for an opt-out order204 No Content when the customer didn't opt in to anything.
400 with error.code400 with error.code, with new codes for sessions.

Because the order is created after Submit returns, move anything that read the 201 response, such as storing policy numbers, into your Contract Issued webhook handler. Match the webhook to your order with data.order_number, and to your item with data.reference_number.

See Submit the order for the full response and error handling.

6. Reset the session after checkout

This step is new. Once an order is submitted, its session is closed. Clear it in the customer's browser when checkout completes, for example on your confirmation page:

teak('clearSession');

Otherwise, a second purchase from the same browser within 120 minutes reuses the closed session, and the widget fails when the customer makes their choice. See After checkout.

7. Update customer links

To show customers their protection links, call Retrieve order details with the reference_id you submitted and the customer's email. It works for orders from either flow. Each protection product on the order has its own links, and each item's status shows whether its protection was issued. See Give the customer their links.

It returns 404 for a few seconds after Submit returns 202, until the order is created. Call it from your Contract Issued handler, or retry after a short wait.

Roll it out

Your account can use both flows at the same time, so you can move over in stages:

  1. Choose the flow per checkout, on your server. Decide before rendering the page, and render only that flow's widget.
  2. Keep each customer on one flow. The two widgets share storage in the customer's browser, so a customer who moves between flows can carry a session from one into the other. Decide once per customer, for example by account or with a cookie, rather than per page view.
  3. Submit with the matching endpoint. A Legacy API quote token only works with POST /orders, and a session ID only works with Submit. Store which flow each checkout used.
  4. Keep order numbers unique across both flows. Teak rejects an order number already used by an order from either flow.
  5. Use one webhook handler. Webhooks look the same for orders from both flows.

To roll back, send checkouts to the Legacy API flow again. Orders already submitted through Order Submit are unaffected.

Migration checklist

  • The teak.js loader and <teak-widget> replace the Legacy API loader and placeholder.
  • No page loads both widgets.
  • Every item has name, referenceNumber, cost, event.location, and product types.
  • Event dates are sent as startDatetime and endDatetime.
  • Costs use a . decimal separator.
  • Every tg('update') is now teak('configure').
  • Callbacks work without a context object, and onErrorCb doesn't expect a message.
  • Your server submits with the session ID and reference_id, and handles 202 and 204.
  • Policy numbers are stored from Contract Issued webhooks, not from the Submit response.
  • teak('clearSession') runs after checkout completes.
  • You've worked through the go-live checklist in sandbox.

When you're ready for production, ask your Teak integration contact to enable TeakOS there too.


Did this page help you?