> This page is for version 🇺🇸 US.
> For other versions, use one of these documentation indexes:
> - 🇦🇺 AU (default): https://docs.humanapproved.com/au/llms.txt
> - 🇬🇧 UK: https://docs.humanapproved.com/uk/llms.txt
> - 🇺🇸 US: https://docs.humanapproved.com/us/llms.txt

> This is HumanApproved's documentation, as Markdown for AI agents. /llms.txt lists every page; /llms-full.txt has them all. If you're helping someone integrate HumanApproved, start with /guides/ai-coding-agents.

# Handling the outcome

> Reference for the HumanApproved embed's onResult callback, its outcomes, fail-open reasons and decline reasons.

Pass `onResult` to `protect()` and it's called once for each outcome of a check or review:

```js
HumanApproved.q.push(["protect", {
  // …key, quote, before, quoteDetails
  onResult: (result) => {
    console.info("HumanApproved", result.outcome, result);
  },
}]);
```

## The result

```ts
type GateResult = {
  outcome: "continue" | "review" | "approved" | "declined" | "expired" | "fail_open";
  reason?: "timeout" | "network" | "http_401" | "http_403" | "http_4xx" | "http_5xx" | "error";
  checkId?: string;   // the check, once HumanApproved has answered
  reviewId?: string;  // the review, for review and what followed it
  quoteRef: string;   // your quote reference
  declineReason?: { code: string; label: string }; // with declined only
  monitored?: true;   // for a monitor-only product
};
```

## Outcomes

| `outcome`   | When                                                    | Comes with                                               |
| ----------- | ------------------------------------------------------- | -------------------------------------------------------- |
| `continue`  | The check found no reason to pause. The step proceeded. | `checkId`                                                |
| `review`    | The step is paused and the agent has the approval link. | `checkId`, `reviewId`                                    |
| `approved`  | The customer approved. The step proceeded.              | `checkId`, `reviewId`                                    |
| `declined`  | The customer declined. The step stays paused.           | `checkId`, `reviewId`, `declineReason` when there is one |
| `expired`   | The approval link expired before the customer approved. | `checkId`, `reviewId`                                    |
| `fail_open` | The step proceeded without a response.                  | `reason`, and `checkId` if the check returned            |

A `review` is followed by `approved`, `declined` or `expired`, or by nothing if the agent goes back to the quote.

`onResult` isn't called for a step that proceeds without a check: after an approval of the same quote, or once checks have stopped after a second fail-open.

## Fail-open reasons

`reason` comes with `fail_open` only:

| `reason`   | Means                                                                                                               |
| ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `timeout`  | No response in time (2.5 seconds, or your `timeoutMs`).                                                             |
| `network`  | No response at all: a blocked host, DNS, offline, or CORS. Check your Content Security Policy and allowed websites. |
| `http_401` | The check was refused for its key, for example a revoked key.                                                       |
| `http_403` | The check was refused for its website: the page's origin isn't on the product's allowed websites.                   |
| `http_4xx` | Another refusal, for example the product isn't published.                                                           |
| `http_5xx` | A server error at HumanApproved.                                                                                    |
| `error`    | Anything else, for example the panel couldn't be shown or the quote couldn't be sent.                               |

## Decline reasons

`declined` comes with `declineReason` when the customer chose one on the approval page.

| `code`          | `label`, as the customer saw it |
| --------------- | ------------------------------- |
| `not_me`        | I didn't ask for this           |
| `details_wrong` | Some details are wrong          |
| `changed_mind`  | I've changed my mind            |
| `other`         | Another reason                  |

```json
{ "code": "changed_mind", "label": "I've changed my mind" }
```

Branch on `code`: it's stable. `declineReason` is left out when the customer gave no reason. Review `not_me` declines: the customer says they didn't ask for this purchase.

## Monitor-only products

A press on a [monitor-only](/us/guides/monitor-only) product goes straight through, and `onResult` is called afterwards with `monitored: true`:

* `continue` with the `checkId` once the check returns, or
* `fail_open` with the `reason` if it fails.

A page that unloads first (for example, a full navigation to your payment page) receives `continue` as it unloads, without a `checkId`. Code that only reads `outcome` sees the same values as for any other product.

## What it doesn't get

`onResult` gets no personal data: no quote details, no mobile, no `userId`.

## How it's called

* After the step continues or pauses, in a microtask, so it doesn't delay the step.
* Anything it throws, or a promise it returns that rejects, is caught and logged to the console.
* If `onResult` isn't a function, it's left out with a console warning, and the step is still protected.

## Example: send outcomes to analytics

**`Google Analytics 4`**

```js title="Google Analytics 4"
onResult: (r) => {
  window.gtag?.("event", "humanapproved_outcome", {
    outcome: r.outcome,
    reason: r.reason,
    decline_reason: r.declineReason?.code,
    monitored: r.monitored === true,
  });
},
```

**`Datadog Browser Logs`**

```js title="Datadog Browser Logs"
onResult: (r) => {
  window.DD_LOGS?.onReady(() => {
    window.DD_LOGS.logger.log(
      `humanapproved ${r.outcome}`,
      { humanapproved: r },
      r.outcome === "fail_open" ? "warn" : "info",
    );
  });
},
```

Alert on a rising share of `fail_open`, especially `http_401`, `http_403` and `network`: they usually mean a revoked key, a missing allowed website, or a Content Security Policy that blocks the embed.