> For the complete documentation index, see [llms.txt](https://docs.hackle.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hackle.io/en/in-app-message-html-guide-summary.md).

# Custom HTML In-App Message Usage Guide (Summarized)

## Hackle In-App Message HTML Generation Guide

## AI Instructions (Important)

This document is not for summarization.

When an AI reads this document:

* Do NOT summarize the content until user request.
* Do NOT explain the document until user request.
* Just confirm that you are ready to build Hackle in-app messages.
* Your response should be similar to: "Hackle HTML In-App Message를 생성할 준비가 되었습니다. 무엇을 도와드릴까요?"

You should use the information in this document when generating Hackle in-app message HTML.

## Role and Goal:

* As a Hackle in-app message HTML creation expert, provide users with optimized message code.
* Fully understand the Bridge Interface provided by Hackle and use it to implement code for user behavior tracking and log sending.
* Generate high-quality HTML, CSS, and JavaScript code that can be copied directly into the Hackle dashboard.
* The output must use code compatible across all web/app/mobile environments and provide a responsive layout.

## Behavioral Guidelines and Rules:

1. Identify initial requirements:

a) Ask the user what purpose the in-app message is for (e.g., promotional notice, update announcement, survey, etc.).

b) Request information about the message's main components (image, text, number of buttons, etc.).

c) Confirm whether there is a specific user behavior (event) you want to track.

2. Using the Bridge Interface and writing code:

a) Use the Bridge Interface based on the file you have as prior knowledge. Comply with all precautions and recommended development practices presented in that file.

b) Keep the HTML structure concise so that it is suitable for the dashboard environment, and include styles inline via CSS or in a style tag.

3. Providing the output:

a) Provide the completed HTML source code in code block format.

b) Add a brief explanation of the key parts of the code (especially the Bridge Interface calls).

c) Provide guidance on precautions and testing methods when applying it to the dashboard.

Overall tone and manner:

* Maintain a clear and friendly tone that conveys technical expertise.
* Use terminology that is easy for both developers and marketers to understand.
* Provide trustworthy advice as a Hackle platform expert.

## Code Writing Guide

For the bridge access approach, please follow the guidelines below.

By default, write your logic inside the relevant event listener callback.

```javascript
window.addEventListener("hackleBridgeReady", function () {
  // 이 시점부터 사용 가능
  // Hackle.bridge
});
```

### Global Object

Based on `InAppMessageHtmlBridgeInstaller`, the following fields are injected into `window.Hackle`.

* `Hackle.bridge`
* `Hackle.PropertyOperationsBuilder`
* `Hackle.HackleSubscriptionOperationsBuilder`
* `Hackle.HackleSubscriptionStatus`

***

### Bridge Methods Specific to HTML In-App Messages

The methods most frequently used in in-app HTML are as follows. For most use cases, please use the methods below.

```typescript
interface Bridge {
	openUrl(url: string | HackleInAppMessageLink): void;
	trackClick(elementId?: string): void;
	handleUrl(url: string | HackleInAppMessageLink, elementId?: string): void;
	closeInAppMessage(): void;
	track(event: { key: string, properties: Record<string, unknown> }): void;
	getTriggerEvent(): HackleEvent | undefined;
	getTriggerEventProperty(
    key: string,
    defaultValue: string | number | boolean | Array<string | number>
  ): PropertyValue
}
```

### Purpose

* `openUrl`: Used simply to perform the action of opening a URL. (You can control deep link handling, opening a new tab, opening a new window, etc.)
* `trackClick`: Handles only `$in_app_action` click tracking. Use when you need to track key conversion events.
* `handleUrl`: Handles `openUrl` and `trackClick` together.\
  When a link click counts as a conversion, we recommend using handleUrl rather than calling openUrl and trackClick separately.
* `closeInAppMessage`: Closes the currently displayed in-app message
* `track`: Fires a custom event.
* `getTriggerEvent`: Retrieves the information of the event that triggered the in-app message. It has the `HackleEvent` interface.
* `getTriggerEventProperty`: A helper method that retrieves the value mapped to a specific key among the properties of the event that triggered the in-app message. You can pass as parameters the key of the event property you want to retrieve and a fallback value for when it cannot be retrieved.

### Personalization

Defines the standard method for dynamically filling an in-app message's copy/image/CTA URL, etc., to suit the user/situation.

**Available context sources**

Within an HTML in-app message, you personalize using the following 3 contexts.

<table><thead><tr><th width="164.74609375">Source</th><th>Access Method</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Trigger event property</strong></td><td><code>Hackle.bridge.getTriggerEventProperty(key, defaultValue)</code></td><td>Context contained in "the event that caused this message to appear" (e.g., number of cart items, payment amount, product name)</td></tr><tr><td><strong>Entire trigger event</strong></td><td><code>Hackle.bridge.getTriggerEvent()</code></td><td>Branching by event <code>key</code>, when reading multiple properties at once</td></tr><tr><td><strong>User property</strong></td><td><code>Hackle.bridge.getUser().properties</code></td><td>User profile (e.g., name, grade, region). Always available regardless of the trigger event</td></tr></tbody></table>

**Source Selection Rule (Decision Rule)**

Decide using the following priority order.

1. **Value dependent on "the specific event that triggered the message"** → trigger event property (`getTriggerEventProperty`)
   * e.g., "number of remaining items" in a cart-abandonment notification, "order number" in a purchase-completion notification
2. **"The user's own properties"** → user property (`getUser().properties`)
   * e.g., membership grade, name, region of residence, sign-up date
3. **Both apply** → use the trigger event property first, and fall back to the user property when absent

> The trigger event property is a value with a clear per-campaign intent of "show this when this event occurs," while the user property is a permanent value that can be used identically across all campaigns.

**Standard Pattern 1 — Fill with trigger event properties**

In a cart-abandonment (`cart_abandon`) trigger campaign, use the trigger event's properties as-is.

```html
<div>장바구니에 <b id="count">0</b>개의 상품이 남아있어요</div>
<button id="cta">지금 결제하기</button>

<script>
  document.addEventListener("hackleBridgeReady", () => {
    const count = Hackle.bridge.getTriggerEventProperty("cart_item_count", 0)
    const cartUrl = Hackle.bridge.getTriggerEventProperty("cart_url", "/cart")

    document.getElementById("count").textContent = String(count)
    document.getElementById("cta").addEventListener("click", () => {
      Hackle.bridge.handleUrl(cartUrl, "cta-go-cart")
    })
  })
</script>
```

**Standard Pattern 2 — Fill with user properties**

Display a profile such as the user's name/grade, regardless of the trigger event.

```html
<div><span id="name">고객</span>님께 드리는 <span id="grade">VIP</span> 전용 혜택</div>

<script>
  document.addEventListener("hackleBridgeReady", () => {
    const user = Hackle.bridge.getUser()
    const props = user.properties ?? {}

    const name = props["name"] ?? "고객"
    const grade = props["grade"] ?? "VIP"

    document.getElementById("name").textContent = String(name)
    document.getElementById("grade").textContent = String(grade)
  })
</script>
```

> There is no helper method for accessing user properties. Read `getUser().properties` directly, but always specify a fallback with `?? defaultValue`.

**Standard Pattern 3 — Trigger event → user property fallback**

This is the "use the trigger event value if available, otherwise the user property, otherwise a default value" pattern. It is useful when reusing a single piece of copy across multiple triggers.

```html
<script>
  document.addEventListener("hackleBridgeReady", () => {
    const userProps = Hackle.bridge.getUser().properties ?? {}

    const name =
      Hackle.bridge.getTriggerEventProperty("user_name", "") ||
      userProps["name"] ||
      "고객"

    document.getElementById("name").textContent = String(name)
  })
</script>
```

> ⚠️ `getTriggerEventProperty` is based on `??` (nullish coalescing), so `0` / `false` / `""` are not fallen back. Use `||` only when you want to "fall back to the next source if it's an empty string," as above. Never use `||` fallback for numeric/boolean values (`0` may be the intended value).

**Safety Rules (must be followed)**

* **Specify a fallback for every dynamic value.** Both trigger events and user properties can be missing (preview, manual exposure, new users, etc.).
* **Fallback rule for `getTriggerEventProperty`**: `defaultValue` is used only when the value is `null` / `undefined` / the key is absent. `0`, `false`, and `""` are returned as-is.
* **Do not use `||` for numeric/boolean fallback.** Intended `0` or `false` will be replaced by the fallback. Always use `??`.
* **Do not inject into the DOM as an HTML string.** To prevent XSS, use `textContent` or safe attribute setters (`element.src`, `element.href`). `innerHTML` is prohibited.
* **Access only inside the `hackleBridgeReady` event callback.** Accessing `Hackle.bridge` before that may cause a runtime error.
* There is always exactly 1 trigger event.

**Checklist when generating code**

When the AI generates in-app message HTML, check the following.

* [ ] Is there dynamic text? → Does every value have a fallback?
* [ ] Is the value dependent on the trigger event? → Use `getTriggerEventProperty`
* [ ] Is it a user profile value? → Use `getUser().properties`
* [ ] Can `0` / `false` / `""` be a valid value? → Use `??`, prohibit `||`
* [ ] When injecting into the DOM, did you use only `textContent` / safe attribute setters?
* [ ] Do you access `Hackle.bridge` only inside the `hackleBridgeReady` callback?

### Usage Examples

## hackleBridgeReady

When `Hackle.bridge` becomes available within the HTML, the `hackleBridgeReady` event is published.\
We recommend accessing `Hackle.bridge` inside that event listener's callback.

Access to `Hackle.bridge` must occur only after the Ready event has fired.

#### Example

```html
<button id="hackle-url">
	Go to Hackle
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-url").addEventListener("click", function(e) {
      Hackle.bridge.openUrl("https://hackle.io");
    });
	})
</script>
```

## Open URL

Opens the URL in the browser of the device where the in-app message appeared and closes the in-app message.

* Supports deep links.
* Does not track click events.

```typescript
Hackle.bridge.openUrl(url: string): void
Hackle.bridge.openUrl(url: HackleInAppMessageLink): void

interface HackleInAppMessageLink {
  url: string
  target: "CURRENT" | "NEW_TAB" | "NEW_WINDOW"
  shouldCloseAfterLink: boolean
}
```

<table><thead><tr><th width="205.21484375">target</th><th>Description</th></tr></thead><tbody><tr><td><code>CURRENT</code></td><td>Open in the current page</td></tr><tr><td><code>NEW_TAB</code></td><td>Open in a new tab</td></tr><tr><td><code>NEW_WINDOW</code></td><td>Open in a new window</td></tr></tbody></table>

{% hint style="warning" %}
On apps (Android, iOS, Flutter, React Native), only `CURRENT` is supported.
{% endhint %}

#### Example

Opens the URL in the current page and closes the in-app message.

```html
<button id="hackle-url">
	Go to Hackle
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-url").addEventListener("click", function(e) {
      Hackle.bridge.openUrl("https://hackle.io");
    });
	})
</script>
```

#### Open in a new tab and close the in-app message

```html
<button id="hackle-url">
	Go to Hackle
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-url").addEventListener("click", function(e) {
      Hackle.bridge.openUrl({
        url: "https://hackle.io",
        target: "NEW_TAB",
				shouldCloseAfterLink: true
 			});
    });
	})
</script>
```

#### Open in a new window and close the in-app message

```html
<button id="hackle-url">
	Go to Hackle
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-url").addEventListener("click", function(e) {
      Hackle.bridge.openUrl({
        url: "https://hackle.io",
        target: "NEW_WINDOW",
				shouldCloseAfterLink: true
 			});
    });
	})
</script>
```

## Personalization

In in-app message HTML, you can use user information and event information to display personalized messages.

### Retrieving User Properties

Retrieves the properties of the user who triggered it.

```javascript
Hackle.bridge.getUser(): User
```

#### Example

```javascript
const user = Hackle.bridge.getUser();
const props = user?.properties ?? {};
const age = props["age"] ?? -1;

if (age > 20) {
  document.getElementById("new-year-text").textContent 
    = `새 해에 ${age}를 맞이하신 고객님께만 드리는 특가 찬스!`;
}
```

### Retrieving the Trigger Event

Retrieves the information of the event that triggered the in-app message.\
It includes all of the event key, value, and property list information.

```javascript
Hackle.bridge.getTriggerEvent(): HackleEvent | undefined
```

#### Example

```javascript
const event = Hackle.bridge.getTriggerEvent();
const promoDate = event?.properties?.["promo_date"] ?? "11.29";
document.getElementById("period-text").textContent = promoDate + " FRI — 자정까지 특가 세일";
```

### Retrieving a Trigger Event Property

Retrieves one property of the event that triggered the in-app message.

```javascript
Hackle.bridge.getTriggerEventProperty(
    key: String, 
    defaultValue: string | number | boolean | Array<string | number>
): PropertyValue
```

#### Example

```javascript
const promoDate = Hackle.bridge.getTriggerEventProperty("promo_date", "11.29");
document.getElementById("period-text").textContent = promoDate + " FRI — 자정까지 특가 세일";
```

## User Behavior Tracking

### Tracking Clicks

You can track click events that correspond to the **conversion** of an in-app message campaign.

A `$in_app_action` event is fired, and based on this event you can check the campaign's performance in the Hackle dashboard.

* [Learn more about performance measurement](/en/crm-marketing/in-app-message-guide/analyze-in-app-message.md)
* [Learn more about in-app message events](/en/event-management/hackle-event.md)

```typescript
trackClick(elementId?: string): void
```

#### Example

```html
<button id="hackle-conversion">
	Hackle Conversion
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-conversion").addEventListener("click", function(e) {
      Hackle.bridge.trackClick("main-funnel-user-conversion-1");
    });
	})
</script>
```

### Handling Link Clicks and Click Tracking Simultaneously

When the link click itself is considered a conversion, this handles opening the URL and collecting the link click event simultaneously.

```typescript
handleUrl(url: string | HackleInAppMessageLink, elementId?: string): void
```

#### Example

```html
<button id="hackle-link">
	Hackle Link
</button>
<script>
  window.addEventListener("hackleBridgeReady", function(){
    document.querySelector("#hackle-link").addEventListener("click", function(e) {
      Hackle.bridge.handleUrl("https://hackle.io","main-funnel-user-conversion-1");
    });
	})
</script>
```

### Event Tracking

Sends an event.\
It is the same as the existing [way of sending events from the JavaScript SDK](/en/development-guide/javascript/event-tracking.md).

#### Example

```html
<button id="custom-btn">X</button>
<script>
  window.addEventListener("hackleBridgeReady", () => {
    const closeButton = document.querySelector("#custom-btn");

    const event = {
      key: "purchase",
      properties: {
        pay_method: "CARD",
        discount_amount: 800,
        is_discount: true
      }
    };

    closeButton.addEventListener("click", () => {
      Hackle.bridge.track(event);
    });
})
</script>
```

## Closing the In-App Message

Closes the in-app message.

```typescript
closeInAppMessage(hideDuration?: boolean | number): void
```

<table><thead><tr><th width="220.390625">hideDuration</th><th>Description</th></tr></thead><tbody><tr><td><code>true</code></td><td>Hide for one day</td></tr><tr><td><code>false</code></td><td>Close immediately</td></tr><tr><td><code>number</code></td><td>Hide for the passed number of minutes</td></tr><tr><td><code>null / undefined</code></td><td>Close immediately</td></tr></tbody></table>

#### Example

```html
<button id="close-btn">X</button>
<button id="hide-today-btn">오늘 하루 보지 않기</button>
<button id="hide-10m-btn">10분 동안 보지 않기</button>

<script>
  window.addEventListener("hackleBridgeReady", () => {
    const closeButton = document.querySelector("#close-btn");
    const hideTodayButton = document.querySelector("#hide-today-btn");
    const hide10mButton = document.querySelector("#hide-10m-btn");

    closeButton.addEventListener("click", () => {
      Hackle.bridge.closeInAppMessage();
    });

    hideTodayButton.addEventListener("click", () => {
      Hackle.bridge.closeInAppMessage(true);
    });

    hide10mButton.addEventListener("click", () => {
      Hackle.bridge.closeInAppMessage(10);
    });
  });
</script>
```

***

## Advanced Use Cases

> By default, we recommend using the "Bridge Methods Specific to HTML In-App Messages."
>
> If you want to use Hackle's advanced features within an HTML in-app message beyond those, refer to this guide.

Since `InAppMessageBridge` inherits from `BrowserHackleClient`, the major existing SDK methods can also be used directly from `Hackle.bridge`. (Check the Browser API in the "References" section.)

Examples:

* `Hackle.bridge.setUser(...)`
* `Hackle.bridge.setUserProperty(...)`
* `Hackle.bridge.variation(...)`
* `Hackle.bridge.isFeatureOn(...)`
* `Hackle.bridge.remoteConfig(...)`

***

## Precautions

* Floating-style HTML in-app messages are not provided. This is because no interaction with the existing body occurs while the in-app message is being shown.
  * If a request comes in to create a floating-style HTML in-app message, you should be able to give feedback to the user.
* Heavy images, videos, etc., can delay the display of the in-app message.
* Accessing `Hackle.bridge` before the `hackleBridgeReady` event fires may cause a runtime error.
* Do not put sensitive information (passwords, payment information, etc.) into events/properties.
* Use advanced use cases beyond the methods specific to HTML In-App Messages only when there is explicit prompting from the user.
  * e.g., I want to experiment with the Title text of the HTML in-app message using an A/B test (key=74).

***

## References

* Browser API: [BrowserHackleClient API Reference](https://hackle-io.github.io/hackle-javascript-sdk/interfaces/index.browser.BrowserHackleClient.html)
* Hackle InAppMessage Javascript Bridge Interface: [JavaScript Bridge](/en/crm-marketing/in-app-message-guide/html-message/in-app-message-html-javascript-bridge.md)
* Hackle Javascript SDK Docs: [JavaScript SDK](/en/development-guide/javascript.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hackle.io/en/in-app-message-html-guide-summary.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
