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.
Step-by-step flow
-
Create a brand. Use
POST /organisations/brand/onboardingto create a brand and get aproductId. -
Create an ImpactPay session. Call
POST /impactpay/sessionsfrom your server with order context and merchant details. You’ll receive animpactPaySessionIdand a short-livedclientSecret. -
Initialise the SDK. On the client, use the
clientSecretto authenticate the session. ekko server-side renders the embedded component inside your page container. -
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.
-
Set up webhooks. Subscribe to webhook events to stay in sync:
IMPACT_PAYMENT→ fired when a contribution is processedIMPACT_PAYMENT_REVERSAL→ fired if a payment is refunded or voided
-
Records and reconciliation. Contributions and reversals appear in Impact Records. Since ekko processes the contribution, settlement shows
fundsFlow = receivablefor 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 (
sandboxorprod) and optional settings likelocale - EkkoElements → used to create and mount ekko components. For the embedded SDK, this provides the
createImpactPayEmbeddedmethod
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 APIcontainer(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 Width | Device Type | Height |
|---|---|---|
| 0–375px | Very small phones | 200px |
| 376–519px | Regular phones | 190px |
| 520–639px | Large phones | 170px |
| 640–767px | Small tablets | 160px |
| 768–1023px | Tablets | 150px |
| 1024–1279px | Laptops/Desktop | 140px |
| 1280px+ | Large Desktop | 130px |
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
codeandmessage
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
| Code | Message |
|---|---|
| sdk_error | An unexpected error occurred in the SDK |
| iframe_load_error | Failed to load the iframe |
| missing_client_secret | Client secret is missing |
| client_secret_expired | Client secret has expired |
| invalid_params | Missing or invalid parameters provided |
| internal_server_error | Internal 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
clientSecretto the frontend - Ensure the container is sized appropriately in your layout so the component is clearly visible
- Store both the
impactPaySessionIdand your ownorderReferencefor reconciliation
Updated 6 months ago
