Troubleshooting
Find what you're seeing, work out which cause it is, and fix it.
Most integration problems don't produce an error. The widget stays empty, Submit succeeds with nothing to issue, or a webhook never arrives. Find what you're seeing below. Each section starts with a quick check that tells you which cause you have.
- The widget doesn't show
- Submit returns 204 but the customer opted in
- No Contract Issued webhook
- Signature never matches
- Other questions
If you have a widget error code, go straight to Widget error codes. For an API error, see the endpoint's errors in the API reference and Error handling.
The widget doesn't show
Check first. Open your browser's developer tools on the checkout page and reload.
- In the Network tab, look for
teak.jsfromicw.helloteak.com, then a request toapi.helloteak.com(orapi.sandbox.helloteak.com) for the quote. - In the Console, run
customElements.get('teak-widget'). It returnsundefinedif the widget never started.
| What you see | Cause |
|---|---|
No teak.js request, or it failed | The script didn't load |
teak.js loaded, no quote request, customElements.get(...) is undefined | configure was rejected |
Console says Configuration is not valid | No widget element on the page |
Console shows E4025, E4026, or E4027 | An item is missing a field |
The quote request returns 401 or 403 | Keys and environment don't match |
| The quote request succeeds, but the widget shows nothing | A product type isn't mapped |
| The quote request succeeds, but there's no offer | No offer for this cart |
| A loading state that never finishes | A request never completed |
| The widget is in the page's HTML, but you can't see it | Your CSS hides it |
The script didn't load
A blocked or failed teak.js is the one failure the widget can't report, because none of its code runs.
- If your site sets a Content Security Policy, allow
https://icw.helloteak.cominscript-src. See Content Security Policy. - Browser extensions such as ad blockers can block the script. Test in a private window with extensions off.
- Use the loader script exactly as given. It loads
teak.jswithtype="module". Without it, the browser reports aSyntaxErrorand the widget never starts. - Detect it in production with the loader's
onerrorhandler, and let checkout continue without protection.
configure was rejected
configure was rejectedIf configure fails basic checks, the widget doesn't start. It might not log a message or call onErrorCb, so check your call against this list:
apiKeyis missing or empty.itemsis missing or empty, for example becauseconfigureran before your cart loaded.- An item has no
cost, or itscostis0. Leave free items out ofitems. Teak doesn't protect items with no price. - A
costisn't a number or a numeric string, for example'$120'or'19,99'. - The call passes
costsOfItems, an option from Teak's earlier widget.
See Item fields for what each item needs.
No widget element on the page
configure needs a <teak-widget> element on the page when it runs. If there isn't one, the console says Configuration is not valid and onErrorCb is called.
- With a framework such as React or Vue, call
configureafter the component that contains the element has rendered. - Check the tag name. A misspelled tag, such as
<element-checkout>, is ignored.
Keys and environment don't match
Sandbox keys need sandbox: true, and production keys need it left out.
sandbox must be the boolean true, not the string 'true'. A string is ignored with a console warning that the option has an invalid type, and the widget calls production with your sandbox key.
A product type isn't mapped
Product types are your inventory system's labels, which Teak maps to protection during onboarding: each item's productTypes, or the id of the widget elements on the page for items without their own. If you send one Teak hasn't mapped for your account, the widget shows nothing, without an error or a console message.
An id you added for another reason, such as styling, is read as a product type too. Style the element with a class instead. If you've started selling a new kind of product, ask your Teak integration contact to map its product type.
While a mismatched element is on the page, teak.isFormComplete() returns false, so a checkout that requires a choice can't continue.
No offer for this cart
Your account may have no offer for these items, for example because they can't be covered or the product isn't set up yet. Check with your Teak integration contact.
After your account's setup changes, a browser that has already loaded the widget can keep the old setup for up to 120 minutes. To see the change straight away, clear the site's data in your browser's developer tools, or test in a private window.
A request never completed
The widget doesn't give up on a slow network, so it can stay loading without calling loadedCb or onErrorCb. Don't let checkout wait for it: add protection only when optInCb fires, and treat "no loadedCb" as "no offer". See When there's no error but no offer.
Your CSS hides it
The widget renders inside its element. If the element or one of its parents has display: none, zero height, or overflow: hidden, the widget is there but invisible. Inspect the element in developer tools to check its size.
Submit returns 204 but the customer opted in
A 204 means that when Submit reached Teak, the session had no saved opt-in. The widget saves the customer's choice to the session in the background, and Teak only knows about choices that were saved.
Check first. In the Network tab, opt in to protection and look for a PATCH request to .../orders/<session ID>/items.
- No
PATCH, or it failed: the choice wasn't saved. See the first two causes below. - The
PATCHsucceeded: compare the session ID in its URL with the value ofteak.get('token')your page sends to your server. If they differ, see You submitted a different session.
The choice couldn't be saved
If saving fails, the widget removes itself, then calls optOutCb and onErrorCb. If your page only listens for optInCb, it still shows the customer as protected, and charges them for protection Teak won't issue.
Handle optOutCb as well as optInCb, and remove the protection price when either optOutCb or onErrorCb fires. See Handle the customer's choice.
Submit arrived before the choice was saved
optInCb fires as soon as the customer chooses, before the save finishes. If your server submits the order in that moment, Teak closes the session first, rejects the save, and Submit returns 204.
This is rare in a normal checkout, where payment takes longer. It's more likely if the customer can pay in one click straight after choosing. Submit the order only after your own order is confirmed.
You submitted a different session
- You read the token too early. Read
teak.get('token')when the customer places the order, not when the page loads. BeforeloadedCbfires, it can return a new ID that the widget doesn't use. - Your cart and checkout are on different domains. The session belongs to one domain, including the subdomain:
www.example.comandcheckout.example.comeach have their own. Read the token on the page where the customer made their choice. - You reused the session after
clearSession. AfterclearSession,teak.get('token')can keep returning the previous session until the page reloads. If your checkout is a single-page app, load the page again after resetting the session, before the next purchase.
A cart-line widget doesn't match a cart line
With one widget per cart line, item-index starts at 0. If it doesn't match an item in items, the offer can appear, but loadedCb never fires and the customer's choice isn't saved.
No Contract Issued webhook
Check first.
- Did Submit return
202? If it returned204, nothing was issued. See Submit returns 204. - Wait two minutes. Teak sends webhooks in batches every minute.
- Call Retrieve order details with your order number. If it still returns
404after a few minutes, Teak didn't create the order: contact your Teak integration contact with the session ID and your order number. - Check your server's access logs for requests to your webhook URL. The Teak Dashboard doesn't show delivery attempts.
Your endpoint returned an error
Teak treats any HTTP response as delivered, including 4xx and 5xx, and doesn't retry. If your logs show the request with an error response, the event is lost. Common causes:
- CSRF protection. Frameworks such as Django and Rails reject
POSTrequests without a CSRF token. Exempt your webhook route. - Authentication. Your endpoint requires a login or an API key that Teak doesn't send. Rely on the signature instead.
- The signature check fails. See Signature never matches.
To stop losing events while you fix it, respond 2xx as soon as you've stored the request, and process it afterwards.
Your URL redirects
If your webhook URL redirects, for example from http to https, to add a trailing slash, or to add www, the POST becomes a GET without the body. Use the final URL exactly.
Teak couldn't reach your endpoint
If your endpoint doesn't respond within 10 seconds, can't be reached, or has an invalid TLS certificate, Teak retries for about 35 minutes and then drops the event. See Delivery.
The subscription doesn't match the event
- The subscription must include Contract Issued.
- Sandbox and production have separate dashboards and separate subscriptions. A sandbox order only sends to subscriptions in the sandbox dashboard.
- The subscription must have a webhook URL, not only an email address.
The protection wasn't issued
Teak can put a contract on hold or reject it instead of issuing it. You then receive Contract On Hold or Contract Void instead of Contract Issued, but only if you subscribe to those events. Teak can also hold an order for review without sending any event. If nothing arrives within 15 minutes, ask your Teak integration contact to check the order.
Signature never matches
Check first. Run your verification function on the test values with the secret so-secret. If that fails, the bug is in your function. If it passes, the problem is the body or the secret your server uses.
The header isn't a signature
If X-Protecht-Signature starts with t: rather than sha256:, the subscription has no secret, so Teak isn't signing requests. Add a secret to the subscription.
Editing a subscription in the dashboard and clearing the hidden secret field removes the secret. Re-enter it before you save.
Your function doesn't use the raw body
Teak signs the body exactly as sent. Anything that changes a single byte breaks the signature:
- Parsing and re-serializing.
JSON.stringify(req.body)doesn't reproduce Teak's spacing: Teak's body has a space after each:and,. Read the raw bytes before any JSON parser runs. - Body parsers that run first. In Express, a global
express.json()consumes the body before your route. Useexpress.raw({ type: 'application/json' })on the webhook route. In Django, userequest.body, notrequest.POSTor Django REST Framework'srequest.data. - Decoding and re-encoding. Non-ASCII characters arrive escaped, for example
é. Keep the body as bytes until you've verified it.
Your function reads the header wrongly
- Remove the
sha256:prefix and the space after it, and compare only the value. - The value is base64, not hex.
You're using the wrong secret
- Use the secret exactly as you entered it in the dashboard, as UTF-8 text. Spaces are part of it. Don't base64- or hex-decode it.
- Each subscription has its own secret, and sandbox and production subscriptions are separate. If several subscriptions send to the same URL, each signs with its own secret.
- The dashboard doesn't show the secret again. If you aren't sure what it is, set a new one on the subscription and deploy it at the same time.
After you change the secret, retries of earlier events are signed with the new one. There's no overlap period, so deploy the new secret at the same moment you save it.
Other questions
Why do I get several Contract Issued webhooks for one order?
Teak sends one for each protected item. Match them to your items with data.reference_number, and to your order with data.order_number.
Why does Retrieve order details return 404 straight after Submit?
Teak creates the order a few seconds after Submit returns 202. Call it from your Contract Issued handler, or retry after a short wait. See After checkout.
The customer opted in on the cart page. Why do they have to choose again at checkout?
The widget remembers the choice across pages only with persist: true. See Showing the widget on more than one page.
Why does onErrorCb fire only once?
onErrorCb fire only once?It's called at most once until the widget loads successfully again. If you call configure again and it fails again, you won't get a second call, so don't rely on onErrorCb to detect every failure.
Should checkout stop if the widget fails?
No. Let the customer complete checkout without protection, and don't submit that order to Teak. See Errors.
Next steps
Didn't find what you're seeing? Look up the error code:
- Look up any code in widget error codes, with its cause and fix.
- Learn how to handle API errors, and when to retry.
Updated 19 days ago
