Skip to main content
The Embeddables new Embed Code 2.0 is now available in beta.
The old Embed Code will be deprecated on Monday November 17th, 2025.

The new Embed Code 2.0

The HEAD script

<script>
window.initEmbeddables = () => {
  // Get the engine domain from the URL parameters
  const engineDomain =
    new URL(window.location.href).searchParams.get('embeddables_engine_domain') ||
    'engine.embeddables.com'
  const urlRoot = engineDomain.startsWith('http') ? engineDomain : 'https://' + engineDomain

  // Inject the bundle script dynamically
  const script = document.createElement('script')
  script.src = `${urlRoot}/bundle.js`
  document.head.appendChild(script)

  const initializeEmbeddables = function () {
    const allUserData = JSON.parse(localStorage.getItem('SavvyFormUserData') || '{}')
    const embeddablesToLoad = [...document.querySelectorAll('savvy, embeddable')].map((el) => {
      const attrs = Object.fromEntries([...el.attributes].map((a) => [a.name, a.value]))
      const flowId = attrs.id
      if (flowId && allUserData[flowId]) {
        attrs.userData = Object.fromEntries(Object.entries(allUserData[flowId]||{}).filter(([sk])=>sk==='current_page_id'||sk.startsWith('split_')))
      }
      return attrs
    })
    const originUrl = window.location.href
    const url =
      `${urlRoot}/init?load=` +
      encodeURIComponent(JSON.stringify({ embeddablesToLoad, originUrl }))

    fetch(url)
      .then((res) => res.json())
      .then((response) => eval('(' + response.init_js + ')(response.embeddables_data)'))
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeEmbeddables)
  } else {
    initializeEmbeddables()
  }
}
window.initEmbeddables()
</script>

The BODY script

This part is unchanged from before.
<savvy id="YOUR_EMBEDDABLE_ID"></savvy>

Upgrading to the Embed Code 2.0

1

Grab the new embed code

Copy the <HEAD> script from above.
2

Replace the old embed code with the new one

In your website or web app, look for a script element (likely in the <HEAD> of your page) that starts with <script>const SAVVY_PRELOAD_IDS=....This is the part to replace with the new script. Make sure that when you’re done, the new one is sandwiched inside two <script> tags.Make sure to do this on a staging URL/environment first, so that you can QA your Stripe changes (described below) on both the old and new embed code.
3

Make any Stripe changes that you need to

See the Stripe section below for a list of things to watch out for and update before pushing your changes live.
If you’re on the Embeddables Pro Plan, your Primary Contact will do this part for you.
4

Test your changes thoroughly

Run some end-to-end tests, including testing any payments and promo codes, to make sure that everything is working as expected.Then push live!

Upgrading Stripe components

The Embed Code 2.0 comes with the latest version of Stripe’s JS SDK, which includes certain breaking changes to account for.See below for a list of things to watch out for and update before upgrading to the new embed code.
The main changes to watch out for are:
  1. The default layout for Stripe components has changed from ‘Tabs’ to ‘Accordion’. (Step 1 below)
  2. The create_element_options settings structure has changed. (Step 2 below)
  3. The applyPromotionCode response structure has changed. (Steps 3-4 below)
1

Set any unset Stripe component 'layout' settings to 'Tabs'

In Stripe’s latest version, the default layout is switching from ‘Tabs’ to ‘Accordion’. Therefore, if you have any Stripe components with the ‘layout’ setting unset, you’ll need to set it to ‘Tabs’ before upgrading to the Embed Code 2.0.
  1. Select your Stripe component in the Web App editor. In the right sidebar under Options, find the Layout setting.
  2. Select the Tabs option (only if no option is currently selected).
2

Update create_element_options if applicable

This step only applies if you’ve customized the create_element_options settings in your Stripe component.
Open your Stripe component’s JSON by clicking the three-dot icon () in the Options tab, then select Edit JSON.Check if the create_element_options key exists. If it contains a billingDetails property with multiple keys set to never, update create_element_options.fields.billingDetails to never.
3

Update your applyPromotionCode function logic to handle both 'success' and 'session' response formats

Stripe’s latest SDK version has changed the response schema of applyPromotionCode: instead of response.success it now returns response.session, which contains a new object format.Make sure your function logic handles both formats, so that you can smoothly upgrade to the new embed code.
If you prefer, you can get your changes ready on the latest version of each Embeddable, and then push all of those versions live at the same time that you push the new embed code live. That way, you can avoid the additional logic of handling both old and new formats in your code.However, the risk here is that there is a window of time when one is live and not the other, or that you later need to roll back for some reason, so take this approach at your own risk!
  1. Find all references to applyPromotionCode in your custom code.
  2. Replace conditional checks that use response.type === 'success' with a structure that supports both formats (see the next step for details on the new format):
if (response.success) {
  // Keep your existing logic here
} else if (response.session) {
  // Add new logic here (see next step)
}
  1. Ensure there are no early return statements or throw errors that would prevent reaching the session branch.
Once you’ve upgrade to the new embed code, and are confident that you won’t need to revert to the old one, you can safely simplify the logic to stop handling the old response.success format.
4

Copy and adapt logic for the new response schema

Copy all logic from inside the response.success branch into the response.session branch.Update field accesses to use the new structure:
  • Replace response.success.total with response.session.total.
  • Access numeric values through minorUnitsAmount:
    • total: response.session.total.total.minorUnitsAmount
    • subtotal: response.session.total.subtotal.minorUnitsAmount
    • discount: response.session.total.discount.minorUnitsAmount
    • appliedBalance: response.session.total.appliedBalance.minorUnitsAmount
    • shippingRate: response.session.total.shippingRate.minorUnitsAmount
    • taxExclusive: response.session.total.taxExclusive.minorUnitsAmount
    • taxInclusive: response.session.total.taxInclusive.minorUnitsAmount
For other keys not listed here, refer to the Stripe Custom Checkout Session object documentation.
5

Add error handling

You can access errors via response.error?.message. See the Stripe applyPromotionCode documentation for details.
6

Test your changes

Test the modified logic thoroughly:
  • With old Stripe component: Apply a promo code and verify it’s reflected in the UI, applied to the amount, and that payment completes successfully.
  • With updated Stripe component: Repeat the same tests.

Example: Before and After

Before:
async function applyPromoCode() {
  try {
    const response = await window.StripeCheckout.applyPromotionCode(
      discountCodeUppercase
    );

    if (response.type === "success") {
      context.setUserData({
        promo_code_message: `Your discount $${(
          response.success.total.discount / 100
        ).toFixed(2)} has been applied`,
        discount_code_applied: discountCodeUppercase,
        subtotal: (response.success.total.subtotal / 100).toFixed(2),
        discount: (response.success.total.discount / 100).toFixed(2),
        total: (response.success.total.total / 100).toFixed(2),
        checking_promo_code: false,
      });
    } else {
      context.setUserData({
        promo_code_message: response.error.message,
        checking_promo_code: false,
      });
    }
  } catch (error) {
    console.error("Error applying promo code:", error);
    context.setUserData({
      promo_code_message: `Error trying to apply the promo code ${userData.discount_code}`,
      checking_promo_code: false,
    });
  }
}
After:
async function applyPromoCode() {
  try {
    const response = await window.StripeCheckout.applyPromotionCode(
      discountCodeUppercase
    );

    let subtotal, discount, total, message;

    if (response.success) {
      // Old response structure
      subtotal = (response.success.total.subtotal / 100).toFixed(2);
      discount = (response.success.total.discount / 100).toFixed(2);
      total = (response.success.total.total / 100).toFixed(2);
      message = `Your discount $${discount} has been applied`;
    } else if (response.session) {
      // New response structure
      subtotal = (
        response.session.total.subtotal.minorUnitsAmount / 100
      ).toFixed(2);
      discount = (
        response.session.total.discount.minorUnitsAmount / 100
      ).toFixed(2);
      total = (response.session.total.total.minorUnitsAmount / 100).toFixed(2);
      message = `Your discount $${discount} has been applied`;
    }

    if (response.success || response.session) {
      context.setUserData({
        promo_code_message: message,
        discount_code_applied: discountCodeUppercase,
        subtotal,
        discount,
        total,
        checking_promo_code: false,
      });
    } else {
      context.setUserData({
        promo_code_message:
          response.error?.message || "Unknown error applying promo code",
        checking_promo_code: false,
      });
    }
  } catch (error) {
    console.error("Error applying promo code:", error);
    context.setUserData({
      promo_code_message: `Error trying to apply the promo code ${userData.discount_code}`,
      checking_promo_code: false,
    });
  }
}