Skip to main content

Get started

Build a host page with your own chart, then load the iC Candle embed as an iframe.

Step 1 — Mount the iframe​

<iframe
id="iccandle"
src="https://embed-iccandle-app.iccandle.ai/en?theme=light&header=false"
title="iC Candle"
allow="clipboard-write; payment"
style="width: 100%; height: 100%; border: 0;"
></iframe>

Use a locale path (/en, /zh, …), optional theme (light | dark | system), and header=false when you want to hide the embed app header.

For the full scanner query-param reference, see Search params.

Do not sandbox the iframe in a way that blocks popups (Google / Cognito sign-in) or top-level navigation (Stripe Checkout).

Step 2 — Send parent origin (billing)​

Your page and the iframe are cross-origin. They cannot share memory or app state; they communicate with window.postMessage.

On iframe load, tell the embed:

  1. Which parent origin hosts it (always).
  2. Optionally that a Stripe checkout just succeeded (?payment=success on your host URL).
  3. Then clean Stripe-related query params from the parent URL.
const EMBED_ORIGIN = "https://embed-iccandle-app.iccandle.ai";
const iframe = document.getElementById("iccandle");

iframe.addEventListener("load", () => {
const win = iframe.contentWindow;
if (!win) return;

// (1) parent-origin — always
win.postMessage(
{ type: "parent-origin", origin: window.location.origin },
EMBED_ORIGIN,
);

// (2) payment-success — only if ?payment=success
const params = new URLSearchParams(window.location.search);
if (params.get("payment") === "success") {
const post = () =>
win.postMessage({ type: "payment-success" }, EMBED_ORIGIN);
// Retry so the embed is ready to receive the message
[0, 600, 1500].forEach((delay) => window.setTimeout(post, delay));

// (3) strip query params
const url = new URL(window.location.href);
["payment", "session_id", "lookup_key"].forEach((p) =>
url.searchParams.delete(p),
);
window.history.replaceState({}, "", url);
}
});
StepMessage / actionWhen
Parent origin{ type: "parent-origin", origin }Every iframe load — so Stripe checkout can return to your domain
Payment success{ type: "payment-success" }Only when the parent URL has ?payment=success (retried at 0 / 600 / 1500 ms)
URL cleanupDelete payment, session_id, lookup_keyAfter posting payment success, via history.replaceState

Some hosts stringify the payload (JSON.stringify({ type: "parent-origin", … })); the embed accepts both objects and JSON strings. See Stripe return.

Step 3 — Listen for embed messages​

Accept messages only from the embed origin. Payloads may arrive as plain objects or JSON strings, so normalize event.data first.

For the full message catalog and payload shapes, see Window messages and Data types.

For iframe hosts, the current embed contract is mostly a namespaced envelope:

type EmbedMessage = {
name?: string;
data?: unknown;
type?: string;
pattern?: unknown;
};

Most outbound messages use name values such as auth.signIn, selector.loading, chart.play, news.eventClicked, and nav.click.

Some pattern-selection flows still send direct objects instead of the name envelope, so your host should inspect both message.name and message.type.

Shared types​

/** OHLC bar — same shape as cacheCandle and replay payloads */
interface Candle {
o: number; // open
h: number; // high
l: number; // low
c: number; // close
timestamp: number; // bar time, unix seconds
}

/** Calendar / economic event (news flow) */
interface NewsEvent {
id: string;
timestamp: number; // unix ms
event_name: string;
metric: string;
forecast: string;
actual: string;
previous: string;
currency: string;
}

Handler skeleton​

window.addEventListener("message", (event) => {
if (event.origin !== EMBED_ORIGIN) return;

let message;
try {
message =
typeof event.data === "string" ? JSON.parse(event.data) : event.data;
} catch {
return;
}

switch (message?.name) {
case "auth.signIn":
localStorage.setItem("iccandle_token", message.data.idToken);
break;
case "selector.loading":
toggleHostLoading(message.data.isLoading); // boolean
break;
case "chart.play":
onChartReplay(message.data);
break;
case "chart.stop":
clearReplayAndOverlays();
break;
case "selector.closeResult":
clearReplayAndOverlays();
break;
case "nav.click":
onEmbedNav(message.data.href); // string
break;
case "news.eventClicked":
onNewsEvent(message.data.event, message.data.similarDetails);
break;
case "news.selectedEventPayloads":
persistSelectedNewsEvents(message.data); // NewsEvent[]
break;
case "news.back":
case "news.backToSimilarEvents":
clearNewsReplayAndEventLine();
break;
case "news.iframeReady":
onEmbedReady();
break;
case "news.analyzeImpact":
onNewsAnalyzeImpact();
break;
case "pattern.classicPatternSelected":
drawPatternRange(message.data?.pattern);
break;
case "pattern.clearClassicPatternSelected":
clearPatternRange();
break;
case "pattern.custom_pattern_selected":
drawTrackedPatternRange(message.data?.pattern);
break;
case "pattern.clear_custom_pattern_selected":
clearTrackedPatternRange();
break;
}
});

name messages (embed → parent)​

nameWhen it firesPayloadHost action
auth.signInSign-in completes inside the iframe (email, password, or OAuth){ name: "auth.signIn", data: { idToken: string } }Store data.idToken as iccandle_token for Bearer API calls
selector.loadingScan or AI prep starts or finishes{ name: "selector.loading", data: { isLoading: boolean } }Show / hide loading UI on your chart or page
chart.playUser starts replay from news or chart analysis flows{ name: "chart.play", data: { isReplay: true, predictCandles?: Candle[] | null, playEndTimestamp: number | null, selectedCandles: Candle[] | null } }Inject replay bars / generated candles and block live ticks until playEndTimestamp
chart.stopReplay is cleared or closed{ name: "chart.stop", data: { isReplay: false, predictCandles?: Candle[] | null, playEndTimestamp: null, selectedCandles: null } } or { name: "chart.stop", data: null }Clear replay candles and replay-specific overlays
selector.closeResultUser closes the active result / analysis panel{ name: "selector.closeResult", data: null }Clear replay and result-specific overlays
nav.clickUser clicks header navigation inside the embed{ name: "nav.click", data: { href: string } }Optional — react to tab changes (for example, hide chart chrome on /news)
news.eventClickedUser selects a calendar / economic event{ name: "news.eventClicked", data: { event: NewsEvent, similarDetails: object | null } }Center chart on the event, draw a vertical line, and highlight the timescale mark
news.selectedEventPayloadsUser selects scanner news events{ name: "news.selectedEventPayloads", data: NewsEvent[] }Persist for timescale marks if your chart supports them
news.backUser leaves news detail or similar-events view{ name: "news.back", data: null }Remove event line, marks, and news replay overlays
news.backToSimilarEventsUser navigates back within the news flow{ name: "news.backToSimilarEvents", data: null }Same as news.back — clear event-specific chart decorations
news.iframeReadySimilar-events iframe finishes initial load{ name: "news.iframeReady", data: null }Optional lifecycle hook for host orchestration
news.analyzeImpactUser requests news impact analysis{ name: "news.analyzeImpact", data: null }Optional — hook into analysis-specific host UI if needed
pattern.classicPatternSelectedUser selects a classic/common pattern match{ name: "pattern.classicPatternSelected", data: { pattern: object } }Highlight the compared pattern date range
pattern.clearClassicPatternSelectedUser clears that classic/common selection{ name: "pattern.clearClassicPatternSelected", data: null }Remove the pattern highlight
pattern.custom_pattern_selectedUser selects a tracked pattern{ name: "pattern.custom_pattern_selected", data: { pattern: object } }Highlight the tracked pattern range
pattern.clear_custom_pattern_selectedUser clears tracked pattern selection{ name: "pattern.clear_custom_pattern_selected", data: null }Remove the tracked-pattern highlight

playEndTimestamp is unix seconds — the bar time where replay should stop.

pattern objects come from scan / pattern-tracker results. Your host only needs the start / end timestamps (or equivalent range fields) to draw a highlight, so inspect one live message in devtools and map those fields to your chart API.

Step 4 — Authenticate​

App routes require a session. Opening the embed without cookies redirects to /{locale}/sign-in.

  • Email / password and OAuth complete inside the iframe (OAuth uses a popup because providers deny framing).
  • On success the embed posts auth.signIn with { data: { idToken } }.
  • Store that token as iccandle_token if your host will call cacheCandle or other APIs.

See the Auth workflow sequence diagram on the introduction page.

Step 5 — Run a scan​

5a — Cache candles​

POST https://scan-service.iccandle.ai/cacheCandle with the selected window:

Authorization: Bearer <iccandle_token>
Content-Type: application/json

{
"candles": [
{ "o": 1.08, "h": 1.09, "l": 1.07, "c": 1.085, "timestamp": 1719878400 }
]
}
FieldNotes
o, h, l, cOpen / high / low / close
timestampUnix seconds (bar time)

Response includes an id — use it as cid in the iframe URL.

5b — Navigate the iframe​

const params = new URLSearchParams({
tk: "10", // top_k (default 10)
symbol: "EURUSD",
res: "60", // match resolution
ref_res: "60", // reference resolution for the cached window
cid: candleId, // from cacheCandle
from: String(startTimestampSec * 1000), // ms — news range helper
to: String(endTimestampSec * 1000), // ms — news range helper
theme: "light",
header: "false",
model: "light", // AI results model: light | pro
temperature: "1", // AI results temperature: 0.5 | 1 | 1.5
});

// optional: &fs=EURUSD,GBPUSD &period=<unixSec>
iframe.src = `${EMBED_ORIGIN}/en?${params}`;

Required for a home scan: symbol, res, ref_res, cid. Without them the embed skips /search.

See Search params for every supported query param, defaults, and route-specific notes.

Step 6 — Drive your chart from messages​

MessageHost responsibility
chart.play / chart.stopInject or clear replay candles / generated candles on your chart
pattern.classicPatternSelected / pattern.custom_pattern_selectedHighlight the compared pattern range
pattern.clearClassicPatternSelected / pattern.clear_custom_pattern_selectedRemove that range
news.eventClicked / news.selectedEventPayloads / news.backNews marks, vertical lines, selected-event state, event replay cleanup
nav.clickOptional — react when the user switches embed tabs (data.href). Not emitted when header=false.

You own the chart library; the embed only signals intent via postMessage.

Minimal layout​

Place your chart and the iframe side by side (or stacked on small screens). Give the iframe a fixed height or a flex child so the results UI can scroll inside the frame.

Prefer header=false in partner embeds so the host chrome stays primary and the iframe focuses on scan results, pattern tracker, and news flows.

Next​

See Search params for the scanner URL contract, Window messages for the full message catalog, Data types for shared payloads, and API reference for routes and protocol details.