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

# Verified Inputs

> Authenticate the caller of an API trigger with a JWT from your own identity provider, and use their verified identity inside the workflow

An API trigger's key answers *"is this call allowed to fire?"* It says nothing about **who** made it. If you expose a workflow to a web app, a partner, or your own backend, you often need to know the actual end user — and you need that identity to be something the caller cannot forge.

**Verified Inputs** lets a caller attach a JWT from an identity provider you've registered. Pinkfish verifies the signature, then hands the validated claims to your workflow as a `_verified` input.

<Note>
  Verified Inputs establishes **who is calling**. It does not change **whose connections run** — the workflow still executes with the trigger owner's connections. See [Tool Sharing and Identity](/tools/sharing-and-identity#triggers-always-run-as-their-owner).
</Note>

## How a call is authenticated

Two independent things travel on the request:

| What        | Header                                       | Answers                                    |
| ----------- | -------------------------------------------- | ------------------------------------------ |
| **API key** | `X-API-KEY` (or embedded in the webhook URL) | Is this call allowed to fire this trigger? |
| **JWT**     | `Authorization: Bearer <jwt>`                | Who is the end user making it?             |

The API key remains required. The JWT is additional.

By default, verification is **opportunistic**: if a Bearer token is present and its issuer matches one of your registered providers, Pinkfish verifies it and injects the claims. If no token is present, the run proceeds without `_verified`. You can make it mandatory — see [Requiring verification](#requiring-verification).

## Registering a trusted identity provider

Go to **Settings → Triggers** and add a provider. Providers are scoped to your organization and can only be managed by an org admin.

| Field                   | Notes                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| **Name**                | Your label for the provider.                                                                    |
| **Issuer**              | Must exactly equal the `iss` claim in the tokens you mint.                                      |
| **Audience**            | **Set by Pinkfish, not by you** — always `org:<your-org-id>`. Mint your tokens with this `aud`. |
| **JWKS URL** or **PEM** | Exactly one. A JWKS URL is strongly preferred, since it supports key rotation. Must be HTTPS.   |
| **Subject claim**       | Which claim identifies the user. Defaults to `sub`.                                             |
| **Allowed algorithms**  | Asymmetric only. `HS*` and `none` are always rejected.                                          |
| **Max token age**       | How old an `iat` may be. Defaults to 300 seconds.                                               |
| **Status**              | `active` or `disabled`.                                                                         |

<Warning>
  **The audience is pinned to your organization on purpose.** Because Pinkfish forces `aud` to `org:<your-org-id>`, a token minted for one organization cannot be replayed against another organization's trigger. Your token issuer must set that exact `aud` value or verification fails with `audience_mismatch`.
</Warning>

Use the **Test** action on a provider to paste a sample JWT and see the resolved subject and claims before you wire anything up.

## What the workflow receives

When verification succeeds, Pinkfish adds a top-level `_verified` key to the workflow's inputs. Its value is the **entire validated claim set** — every claim the token carried, verbatim:

```json theme={null}
{
  "customerId": "acme-42",
  "_verified": {
    "iss": "https://auth.example.com/",
    "aud": "org:org_abc123",
    "sub": "user_9f2c",
    "email": "dana@example.com",
    "exp": 1782000000,
    "iat": 1781999700,
    "role": "admin"
  }
}
```

Branch on it like any other input — for example, use `_verified.sub` as the record owner rather than trusting a caller-supplied `userId`.

<Note>
  **`_verified` cannot be spoofed.** Pinkfish strips any caller-supplied `_verified` key from the inputs of *every* run-creation path — API, schedule, email, manual, and bulk — before it re-adds a genuinely verified one. A workflow default cannot reintroduce it either. If the key was present, the attempt is logged.

  So inside a workflow, the presence of `_verified` always means a real token verified against one of your registered providers.
</Note>

Each run also records three immutable audit fields, surfaced as a **"Verified caller"** badge in the run details: whether a JWT was presented, the verified subject, and which provider validated it.

## Requiring verification

Opportunistic verification means an unauthenticated caller still runs the workflow — just without `_verified`. To reject those calls outright, turn on `requireVerifiedRequest` for the trigger. The request is then refused **before any run is created**.

Turn on **Require verified request** — *"Reject API requests that lack a verified Trusted-IdP identity (JWT). Off by default."* You'll find it:

* On a **workflow API trigger**, in the trigger editor alongside the API key fields.
* On an **agent API channel**, in the agent editor's Channels section.

It can also be set through the API — it's a `requireVerifiedRequest` field on the trigger, and agent API channels have a dedicated endpoint (`PUT /api/useragents/{userAgentId}/channels/api/require-verified-request`).

<Warning>
  If you rely on verified identity for authorization, you must turn this on. It is **off by default**, and while it is off a caller who simply omits the `Authorization` header gets a successful run with no `_verified` key at all — so a workflow that reads `_verified.sub` would see nothing rather than an error.
</Warning>

## Failure responses

A token whose issuer matches a registered provider but which fails verification is **always** rejected with `401`, whether or not verification is required. A missing token is rejected only when `requireVerifiedRequest` is on.

Responses follow the OAuth bearer-token convention:

```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="expired"
Content-Type: application/json

{ "error": "expired", "error_description": "JWT has expired" }
```

| `error`                 | Meaning                                                         |
| ----------------------- | --------------------------------------------------------------- |
| `verification_required` | The trigger requires a verified request and none was presented. |
| `unknown_issuer`        | No registered provider matches the token's `iss`.               |
| `signature_invalid`     | Signature does not verify against the provider's keys.          |
| `audience_mismatch`     | `aud` is not `org:<your-org-id>`.                               |
| `expired`               | The token's `exp` has passed.                                   |
| `token_too_old`         | The token's `iat` exceeds the provider's max token age.         |
| `algorithm_not_allowed` | The signing algorithm is not in the provider's allowed list.    |
| `invalid_token`         | The token is malformed.                                         |

<Note>
  A Bearer token whose `iss` matches **no** registered provider is ignored rather than rejected, so an unrelated `Authorization` header won't break a trigger — unless `requireVerifiedRequest` is on, in which case it counts as "no verified identity presented."
</Note>

## Where it applies

| Surface                             | Verified Inputs                                                                                                                                |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Workflow **API triggers**           | Yes — verify, inject `_verified`, optional enforcement.                                                                                        |
| **Agent API channels**              | Yes — the verified claims travel as metadata rather than workflow inputs.                                                                      |
| **Webhook triggers**                | No. Webhook deliveries authenticate by HMAC signature from the sending provider, so the JWT path is skipped and the payload is left untouched. |
| Schedule, email, app-event triggers | No JWT is verified. Any caller-supplied `_verified` is still stripped.                                                                         |

## Relationship to A2A end-user connections

Both features verify a caller's JWT against the same trusted-provider registry, but they do different things with the result.

* **Verified Inputs** treats the identity as *data*. The run still executes with the trigger owner's connections.
* **A2A end-user connections** treat the identity as an *execution principal*. Each external caller resolves connections against their own account, and the call is rejected if no verified identity is presented.

Reach for Verified Inputs when you want the workflow to know who asked. Reach for A2A end-user connections when each caller must act as themselves against the third-party app.

## Related

* [Tool Sharing and Identity](/tools/sharing-and-identity) — whose connections a triggered run uses
* [Triggers](/agentic-editor/triggers) — creating API and other triggers
* [API Reference → Authentication](/api-reference/introduction) — trigger API keys and webhook URLs
