Embedded SDK

The embedded SDK runs inside your existing order confirmation page. It adds a modular component that shows customers the impact of their purchase and gives them the option to contribute via ekko’s hosted payment flow. Because it is embedded in an existing page, the experience feels seamless and native.

Embedded SDK

Step-by-step flow

  1. Create a brand. Use POST /organisations/brand/onboarding to create a brand and get a productId.

  2. Create an ImpactPay session. Call POST /impactpay/sessions from your server with order context and merchant details. You’ll receive an impactPaySessionId and a short-lived clientSecret.

  3. Initialise the SDK. On the client, use the clientSecret to authenticate the session. ekko server-side renders the embedded component inside your page container.

  4. Customer reviews and contributes. The embedded component is shown on the confirmation page. Customers see their footprint and can contribute using ekko’s hosted payment.

  5. Set up webhooks. Subscribe to webhook events to stay in sync:

    • IMPACT_PAYMENT → fired when a contribution is processed
    • IMPACT_PAYMENT_REVERSAL → fired if a payment is refunded or voided
  6. Records and reconciliation. Contributions and reversals appear in Impact Records. Since ekko processes the contribution, settlement shows fundsFlow = receivable for the client’s service-fee share.

SDK architecture overview

The ekko Web SDK exposes two main classes:

  • Ekko → entry point of the SDK. Initialise with the environment (sandbox or prod) and optional settings like locale
  • EkkoElements → used to create and mount ekko components. For the embedded SDK, this provides the createImpactPayEmbedded method

Example initialisation

import { Ekko } from '@ekko-earth/ekko-js'

const ekkoInstance = new Ekko('sandbox', { locale: 'en-GB' })

const elements = ekkoInstance.elements({ clientSecret: 'your-client-secret' })

const impactPayElement = elements.createImpactPayEmbedded({
  container: document.getElementById('impactpay-embedded'),
  impactPaySessionId: 'ips_12345'
})

Key inputs

  • impactPaySessionId (string, required) → returned from the ImpactPay Sessions API
  • container (HTMLElement, required) → the DOM element where the embedded component is injected and rendered
#impactpay-embedded {
  width: 100%;
  max-width: 600px; /* example */
  margin: 0 auto;
}

Behaviour and requirements

  • Sizing → designed to slot into your confirmation page without altering the layout
  • Default behaviour → renders inside the specified container at 100% width. Height adjusts to fit content
  • Container requirements → provide a container with a fixed width relative to your page layout

ekko SDK height configuration

To make sure the embedded component always looks its best, set a fixed height on the container. The SDK uses height: 100% internally, so you’ll want to define height explicitly (not min-height). Here’s our recommended height map:

Screen WidthDevice TypeHeight
0–375pxVery small phones200px
376–519pxRegular phones190px
520–639pxLarge phones170px
640–767pxSmall tablets160px
768–1023pxTablets150px
1024–1279pxLaptops/Desktop140px
1280px+Large Desktop130px

Example CSS:

/* Base style for the embedded container */
#impactpay-embedded {
  margin: 0 auto;
  width: 100%;
  height: 200px;
  overflow: visible;
}

/* Responsive heights */
@media (min-width: 376px) {
  #impactpay-embedded { height: 190px; }
}
@media (min-width: 520px) {
  #impactpay-embedded { height: 170px; }
}
@media (min-width: 640px) {
  #impactpay-embedded { height: 160px; }
}
@media (min-width: 768px) {
  #impactpay-embedded { height: 150px; }
}
@media (min-width: 1024px) {
  #impactpay-embedded { height: 140px; }
}
@media (min-width: 1280px) {
  #impactpay-embedded { height: 130px; }
}

Integration examples

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Ekko Embedded ImpactPay</title>
</head>
<body>
  <div id="impactpay-embedded"></div>
  <script type="module">
    import { Ekko } from './node_modules/@ekko-earth/ekko-js/dist/ekko-js.esm.js'

    const ekkoInstance = new Ekko('sandbox')

    const elements = ekkoInstance.elements({ clientSecret: 'clientSecret' })

    const impactPayElement = elements.createImpactPayEmbedded({
      container: document.getElementById('impactpay-embedded'),
      impactPaySessionId: '{impactPaySessionId}'
    })
  </script>
</body>
</html>

React

import React, { useEffect, useRef } from 'react'
import { Ekko } from '@ekko-earth/ekko-js'

const ekkoInstance = new Ekko('sandbox')
const elements = ekkoInstance.elements({ clientSecret: '{clientSecret}' })

const ImpactPayEmbedded = () => {
  const containerRef = useRef(null)

  useEffect(() => {
    if (containerRef.current) {
      elements.createImpactPayEmbedded({
        container: containerRef.current,
        impactPaySessionId: '{impactPaySessionId}'
      })
    }
  }, [])

  return <div ref={containerRef}></div>
}

export default ImpactPayEmbedded

Events

The SDK emits events so your application can react in real time. Subscribe to these events to update your systems or capture analytics.

  • ekko:ready → fired when the ImpactPay component has rendered and is ready for interaction
  • ekko:balance-impact-clicked → fired when the user clicks the “Support climate action” button inside the element.
  • ekko:error → fired when the SDK encounters a critical error. Includes a structured payload with code and message

Detail/payload structure:

{
  "carbonImpact": {
    "grams": 5655,
    "ounces": 199,
    "compensation": {
      "compensationValue": 0.047,
      "serviceFee": 0,
      "currencyCode": "GBP"
    }
  }
}
{
  "impactPaySessionId": "6ec4cc9e-9318-429d-9be4-612dfe47d84e",
  "impactPayUrl": "https://impactpay-sandbox.ekko.earth//en-GB/carbon?referenceId=6ec4cc9e-9318..."
}
{
  "code": "client_secret_expired",
  "message": "Client secret has expired"
}

Example event subscription

// Store the returned element
const impactPayElement = elements.createImpactPayEmbedded({ 
    container: document.getElementById('impactpay-page'),
    impactPaySessionId: '${impactPaySessionId}',
}); 

// Listen directly on the custom element (not the container)
impactPayElement.addEventListener('ekko:ready', (event) => {
  console.log('ekko element is interactive', event.detail);
});

impactPayElement.addEventListener('ekko:balance-impact-clicked', (event) => {
  console.log('Support climate action clicked:', event.detail);
});

impactPayElement.addEventListener('ekko:skipped', (event) => {
  console.log('Skipped to order confirmation:', event.detail);
});

impactPayElement.addEventListener('ekko:error', (event) => {
  console.error('ekko element error:', event.detail);
});

Note: In TypeScript, the DOM addEventListener callback is typed to receive a generic Event, which doesn’t have a detail property. To access the Custom Event’s detail, you’ll need to cast it to CustomEvent

Error handling

The SDK has built-in safeguards to ensure your confirmation flows are never blocked. Error events follow a standardised structure, so you can capture them in your own telemetry or monitoring systems.

Subscribe to the error event to capture and handle errors.

Error codes

CodeMessage
sdk_errorAn unexpected error occurred in the SDK
iframe_load_errorFailed to load the iframe
missing_client_secretClient secret is missing
client_secret_expiredClient secret has expired
invalid_paramsMissing or invalid parameters provided
internal_server_errorInternal server error

Best practices

  • Use the embedded SDK when you control the confirmation page and want a lightweight, native-feeling integration
  • Always create sessions server-side and only pass the clientSecret to the frontend
  • Ensure the container is sized appropriately in your layout so the component is clearly visible
  • Store both the impactPaySessionId and your own orderReference for reconciliation