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 API | TeakOS | |
|---|---|---|
| Widget script | client-widget.min.js?v=4 | teak.js |
| Global | tg | teak |
| Where the widget renders | <div id="tg-placeholder"> | <teak-widget> |
get('token') returns | A quote token (JWT) | The session ID |
| Cart changes | tg('update', ...) | teak('configure', ...) again |
| API base URL | https://api.protecht.com/api/v2 | https://api.helloteak.com/v1, or https://api.sandbox.helloteak.com/v1 in sandbox. The old base URL keeps working. |
| Submitting an order | POST /orders, with items, prices, and event details | POST /orders/{session_id}/submit, with no items or prices |
| Submit response | 201 with the order and its policies | 202, and the order is created a few seconds later |
| Policy numbers | In the submit response | In the Contract Issued webhook |
| Customer links | Order metadata | Retrieve order details |
| After checkout | Nothing | teak('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/tokenas before. It still expires after 5 minutes. Send it asAuthorization: Bearer <token>;JWT <token>also still works. - Billing. Teak still bills you for issued protection on your regular invoice, and
paymentis 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_numberin 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
- Ask your Teak integration contact to enable TeakOS in sandbox. Until it's enabled, Submit returns
403. - 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.
- 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>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
configureRename 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 API | TeakOS |
|---|---|
cost, with a . or , decimal separator | cost, 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 used | name: required |
referenceNumber on each item: optional | referenceNumber: required. It comes back in webhooks as reference_number. |
event.location: not used | event.location: required |
| Product types: not used | productTypes 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 UTC | event.startDatetime, an ISO 8601 date-time |
event.endDate and event.endTime, in UTC | event.endDatetime, an ISO 8601 date-time |
referenceNumber at the top level: required | Not needed. Your order number goes to Submit instead. |
clearSelection | Not supported. Send teak('clearSession') to make the customer choose again. |
currency, locale, persist, sandbox, and the callbacks are unchanged.
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 API | TeakOS |
|---|---|
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. |
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
isProtectedandisFormComplete. See Context. onErrorCbreceives the session ID, not an error message. When the widget fails, it also callsoptOutCbwith no context, so writeoptOutCbto 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 /orders | POST /orders/{session_id}/submit |
|---|---|
token | The session ID, in the path |
order_number | reference_id |
currency | Not sent: taken from the session |
items | Not sent: taken from the session |
event | Not sent: set on each item in the widget |
customer | customer, unchanged. first_name, last_name, and email are required. |
billing_address.address1 | billing_address.address_1 |
billing_address.address2 | billing_address.address_2 |
billing_address.zip_code | billing_address.postal_code |
billing_address.city, state, country | Unchanged |
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.
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 API | TeakOS |
|---|---|
201 with the order and its policies | 202 Accepted. Teak creates the order a few seconds later. |
204 for an opt-out order | 204 No Content when the customer didn't opt in to anything. |
400 with error.code | 400 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:
- Choose the flow per checkout, on your server. Decide before rendering the page, and render only that flow's widget.
- 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.
- 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. - Keep order numbers unique across both flows. Teak rejects an order number already used by an order from either flow.
- 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.jsloader 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
startDatetimeandendDatetime. - Costs use a
.decimal separator. - Every
tg('update')is nowteak('configure'). - Callbacks work without a context object, and
onErrorCbdoesn't expect a message. - Your server submits with the session ID and
reference_id, and handles202and204. - 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.
Updated 19 days ago
