> ## Documentation Index
> Fetch the complete documentation index at: https://formbricks.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Lifecycle Events

> Subscribe to Formbricks events to find out when a survey was actually shown, answered or closed in your app.

Calling `formbricks.track()` does not mean a survey appeared. Formbricks still checks the cooldown period, recontact options, targeting and the percentage setting, and often decides not to show anything.

Lifecycle events tell you what really happened, so your app can react to the survey it actually displayed.

## The events

| Event                           | Fires when                                                                                               | Payload                               |
| ------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `formbricks_survey_shown`       | A survey becomes visible (once per appearance)                                                           | `{ surveyId }`                        |
| `formbricks_response_submitted` | The first answer is stored (`finished: false`) and again when the survey is completed (`finished: true`) | `{ surveyId, responseId?, finished }` |
| `formbricks_survey_closed`      | The survey is dismissed or finishes (once per appearance)                                                | `{ surveyId }`                        |
| `formbricks_setup_successful`   | `setup()` finished — the SDK is ready                                                                    | `{ workspaceId }`                     |
| `formbricks_action_tracked`     | An action was tracked (code or no-code)                                                                  | `{ action }`                          |

`responseId` is the id of the stored response — the same id you see in the Responses tab — so your app can link its own analytics or session replays to the exact response. It is optional: when the id is not available the key is absent, and GTM's dataLayer carries `null` for it.

`formbricks_survey_shown` reports what the respondent saw, so every appearance is paired with a `formbricks_survey_closed`. The Displays number in your Formbricks dashboard counts what reached the server instead, so it can be lower if the network dropped.

## Listening for events

```js theme={null}
formbricks.on("formbricks_survey_shown", (payload) => {
  console.log("Survey shown:", payload.surveyId);
});

formbricks.on("formbricks_response_submitted", (payload) => {
  if (!payload.finished) {
    console.log("First answer stored:", payload.responseId);
  }
});
```

You can subscribe before or after `formbricks.setup()`, and your listeners stay registered after `formbricks.logout()`. `window.formbricks` exists as soon as the SDK script has loaded — `setup()` does not need to have run — so the reliable order is: load the script, register your listeners, then call `setup()`.

<Note>
  Events are delivered live, not replayed: a listener registered after an event fired will not receive it.
  This matters most for `formbricks_setup_successful` — register it before calling `setup()`. If you only need
  to know when your own `setup()` call finished, you can also simply `await` the promise it returns; the event
  is for code that did not make that call, like a decoupled module or a tag. On GTM the dataLayer buffers, so
  its triggers are immune to this ordering.
</Note>

## Stopping a listener

`formbricks.on()` returns a function that removes the listener:

```js theme={null}
const unsubscribe = formbricks.on("formbricks_survey_closed", handleClose);

// later
unsubscribe();
```

If you kept the handler in a variable, `formbricks.off()` does the same:

```js theme={null}
formbricks.off("formbricks_survey_closed", handleClose);
```

## Example: show a survey once per session

```js theme={null}
let surveyShown = false;

formbricks.on("formbricks_survey_shown", () => {
  surveyShown = true;
});

function onCheckout() {
  if (surveyShown) return;
  formbricks.track("checkout_completed");
}
```

<Note>
  A survey that is never shown sends no events at all. That silence is the signal: it means the user did not
  see a survey, so nothing needs to be recorded.
</Note>

<Tip>
  There is no separate "finished" event. `formbricks_survey_closed` covers both dismissing and completing a
  survey — a `formbricks_response_submitted` with `finished: true` for the same `surveyId` is what tells them
  apart. Correlate on the id, not on arrival order: that event is sent once the server has stored the
  response, so it normally lands before the close but can follow it, and if the response cannot be saved it
  never lands at all.
</Tip>

## Google Tag Manager

The same events, under the same names, are pushed to `window.dataLayer` automatically — no `formbricks.on()`
call needed. Create a GTM Custom Event trigger matching the event name (for example
`formbricks_setup_successful`) and read the payload from Data Layer Variables under the `formbricks` key.
See [Google Tag Manager](/docs/surveys/website-app-surveys/google-tag-manager) for the full setup.

<Note>
  Lifecycle events are available in the JavaScript SDK. Support for React Native, iOS, Android and Flutter is
  coming in a later release.
</Note>
