> For the complete documentation index, see [llms.txt](https://docs.amply.tools/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amply.tools/developer-guide/gating-actions.md).

# Gating actions

A **gate** lets a campaign pause the user's flow at a moment you choose, run an action (e.g. a rewarded ad), and resume — or back out — based on the outcome. For the product picture and when to reach for this, read [Gating an action](/concepts/gating-an-action.md) first. This page is the wiring.

There are two halves, and they live in different places:

1. **At the call site** — instead of firing an event and moving on, you `trackGated` the event and `await` a decision before doing the next step. This is opt-in per call site: a moment is gate-able only where you do this.
2. **At app startup** — you `registerGate` for the URLs that are outcome-bearing (the rewarded ad, the survey), supplying a presenter that runs the action and reports how it ended. Everything you don't register stays an ordinary fire-and-forget [deeplink](/developer-guide/handling-deeplinks.md).

If no campaign gates the event, or the SDK isn't ready, or anything fails, the decision is **proceed** — the gate is fail-open and never traps the user.

## 1. Gate the call site

{% tabs %}
{% tab title="iOS (Swift)" %}

```swift
func handleExport() async {
    let decision = await amply.trackGated(event: "ExportTapped", properties: [:])
    guard case .proceed = decision else { return }   // user backed out — don't export
    exporter.export(currentDocument)                  // your code, your live context
}
```

`trackGated` returns a `GateDecision` — `.proceed(reason:)` or `.cancelled`. Use the plain `guard case .proceed` form unless you specifically want the reason (`.completed` vs `.failOpen`, e.g. to grant a reward only on a real completion).
{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
lifecycleScope.launch {
    val decision = amply.trackGated("ExportTapped")
    if (decision is GateDecision.Proceed) {
        exporter.export(currentDocument)   // your code, your live context
    } // GateDecision.Cancelled -> user backed out, don't export
}
```

`trackGated` is a `suspend` function — call it from a coroutine. Match `GateDecision.Proceed(reason)` if you need `ProceedReason.Completed` vs `FailOpen`.
{% endtab %}

{% tab title="React Native (TS)" %}

```ts
async function handleExport() {
  const decision = await amply.trackGated('ExportTapped');
  if (decision.outcome !== 'proceed') return;   // user backed out
  await exporter.export(currentDocument);
}
```

The promise resolves to `{ outcome: 'proceed', reason: 'completed' | 'failOpen' }` or `{ outcome: 'cancelled' }`. It **never rejects** — any failure resolves to `proceed`.
{% endtab %}
{% endtabs %}

Track the same event with plain `track` elsewhere and it won't gate there. Use `trackGated` only where the call site can honor a wait.

## 2. Register what runs at the gate

Register once, at startup, for each outcome-bearing URL. The presenter runs the action and reports how it ended: completed, dismissed by the user, or unavailable (no fill, failed to load, not ready).

{% tabs %}
{% tab title="iOS (Swift)" %}

```swift
final class RewardedAdPresenter: CampaignPresenter {
    func present(params: [String: String], info: [String: Any], resolution: CampaignResolution) {
        let reward = Int(params["reward"] ?? "") ?? 0   // params come from the campaign URL query
        rewardedAd.show(reward: reward) { result in
            switch result {
            case .earned:  resolution.resolve(result: .completed)
            case .closed:  resolution.resolve(result: .dismissed)     // user backed out
            case .noFill, .failed: resolution.resolve(result: .unavailable)  // -> proceeds (fail-open)
            }
        }
    }
    func dismiss() { rewardedAd.tearDown() }   // SDK calls this if the gate is abandoned
}

amply.registerGate(baseUrl: "yourapp://ad", presenter: RewardedAdPresenter(),
                   onAbort: .cancel, timeoutMs: 60_000)
```

{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
class RewardedAdPresenter : CampaignPresenter {
    override fun present(params: Map<String, String>, info: Map<String, Any>, resolution: CampaignResolution) {
        val reward = params["reward"]?.toIntOrNull() ?: 0
        rewardedAd.show(reward) { result ->
            when (result) {
                Result.EARNED -> resolution.resolve(CampaignResult.Completed)
                Result.CLOSED -> resolution.resolve(CampaignResult.Dismissed)
                else          -> resolution.resolve(CampaignResult.Unavailable)
            }
        }
    }
    override fun dismiss() { rewardedAd.tearDown() }
}

amply.registerGate("yourapp://ad", RewardedAdPresenter(), onAbort = AbortPolicy.Cancel, timeoutMs = 60_000)
```

{% endtab %}

{% tab title="React Native (TS)" %}

```ts
await amply.registerGate(
  'yourapp://ad',
  (params, info, resolution) => {
    showRewardedAd(Number(params.reward ?? 0))
      .then(r => resolution.completed())   // or .dismissed() / .unavailable()
      .catch(() => resolution.unavailable());
  },
  { onAbort: 'cancel', timeoutMs: 60_000 },
);
```

{% endtab %}
{% endtabs %}

* **Match by base URL.** Register `yourapp://ad`; a campaign firing `yourapp://ad?type=rewarded&reward=10` matches it, and the query (`type`, `reward`, …) arrives as `params`. One registration serves many campaign variants — the dashboard tunes the parameters with no app release.
* **`onAbort`** decides what a user dismissal means: `cancel` (the gate's `.cancelled` — the flow backs out) or `proceed` (continue anyway). It **defaults to `cancel`**, which is what you want for value-exchange gates like rewarded ads; pass `proceed` for gates that should never hold the flow back.
* **`dismiss()` is required** on the presenter (there is no default). The SDK calls it when the gate is abandoned — caller cancelled, or the timeout fired — so you can tear down any UI you put on screen. Implement it even if it's a no-op for your action.
* **`timeoutMs`** is the fail-open deadline (default 60s, paused while the app is backgrounded). If the presenter never reports, the gate proceeds.
* **Outcomes, strictly:** `dismissed` is a *user-initiated* close only. Every infrastructure failure — no fill, failed to show, not ready — is `unavailable`, which proceeds. Never map an infra failure to `dismissed`, or you'd cancel the user's action for your own outage.

## Consent and permissions are not gates

Don't `registerGate` ATT / privacy-consent / push-permission URLs. Route them as ordinary [deeplinks](/developer-guide/handling-deeplinks.md): present the prompt, record the answer as a [user attribute](/developer-guide/user-attributes.md), and let the flow continue. See [Gating an action — When not to gate](/concepts/gating-an-action.md#when-not-to-gate).

## Related

* [Gating an action](/concepts/gating-an-action.md) — the product model, use cases, and the safety promise
* [Handling deeplinks](/developer-guide/handling-deeplinks.md) — fire-and-forget actions (everything you don't gate)
* [Tracking events](/developer-guide/tracking-events.md) — `track` vs `trackGated`


---

# 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:

```
GET https://docs.amply.tools/developer-guide/gating-actions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
