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

# Workload Identity Federation

> Call the Dodo Payments API from CI pipelines and cloud workloads without a stored API key. Exchange a short-lived OIDC token from your platform for a short-lived Dodo token.

Workload identity federation lets a workload call the Dodo Payments API with no stored API key. The workload gets a signed OIDC token (a JWT) from its own platform, for example GitHub Actions or Google Cloud. Dodo Payments verifies the JWT and gives back a short-lived Dodo token. The token expires in 1 hour or less, except for `terraform_cloud`, where it lasts until the run phase ends.

> **[Dashboard Setup](https://docs.dodopayments.com/developer-resources/workload-identity/dashboard-setup.md)**
>
> Add an identity provider and a mapping, then monitor tokens and events.

> **[SDK Usage](https://docs.dodopayments.com/developer-resources/workload-identity/sdk-usage.md)**
>
> Configure the SDKs to do the exchange and the refresh for you.

> **[Provider Guides](https://docs.dodopayments.com/developer-resources/workload-identity/providers/github-actions.md)**
>
> Step-by-step setup for each supported platform.

> **[Terraform](https://docs.dodopayments.com/developer-resources/workload-identity/terraform.md)**
>
> Run the Terraform provider with no API key in your pipeline.

## Why Use Workload Identity

An API key is a long-lived secret. You must store it in your CI system or your cloud, rotate it, and remove it when a person leaves. If the key leaks, an attacker can use it until you delete it.

With workload identity federation:

* **No secret to store.** The workload keeps only the identity provider id (`wip_...`). This id is not a secret.
* **Short-lived credentials.** Each Dodo token expires in 1 hour or less. A `terraform_cloud` token lasts until the end of the run phase. To continue, the workload does a new exchange.
* **Precise trust.** A mapping trusts only the JWTs that match its conditions, for example one repository and one branch.
* **Same permissions model.** The token gets the scopes of the mapping, and the usual API key rate limits apply.
* **Audit.** Each exchange, failure, revocation, and configuration change writes an event that you can see in the dashboard.

## Supported Platforms

| Provider type | Platform | Guide |
| - | - | - |
| `github_actions` | GitHub Actions | [GitHub Actions](https://docs.dodopayments.com/developer-resources/workload-identity/providers/github-actions.md) |
| `gitlab` | GitLab CI/CD (GitLab.com or self-managed) | [GitLab](https://docs.dodopayments.com/developer-resources/workload-identity/providers/gitlab.md) |
| `terraform_cloud` | HCP Terraform or Terraform Enterprise | [Terraform Cloud](https://docs.dodopayments.com/developer-resources/workload-identity/providers/terraform-cloud.md) |
| `gcp` | Google Cloud (Cloud Run, GCE, GKE metadata server) | [Google Cloud](https://docs.dodopayments.com/developer-resources/workload-identity/providers/google-cloud.md) |
| `vercel` | Vercel | [Vercel](https://docs.dodopayments.com/developer-resources/workload-identity/providers/vercel.md) |
| `kubernetes` | Kubernetes, including Amazon EKS | [Kubernetes and EKS](https://docs.dodopayments.com/developer-resources/workload-identity/providers/kubernetes.md) |
| `aws` | AWS IAM outbound identity federation | [AWS](https://docs.dodopayments.com/developer-resources/workload-identity/providers/aws.md) |
| `custom` | Any OIDC issuer with an HTTPS URL | [Custom OIDC](https://docs.dodopayments.com/developer-resources/workload-identity/providers/custom-oidc.md) |

## Key Concepts

> **Identity provider**
>
> An identity provider (`wip_...`) tells Dodo Payments which issuer to trust. It has a provider type, an issuer URL, and an audience. Each provider belongs to one mode, test mode or live mode. A business can have 50 providers.

> **Mapping**
>
> A mapping (`wim_...`) belongs to a provider. It has a list of claim conditions, a list of API scopes, an optional list of allowed CIDR ranges, and a token lifetime. A provider can have 50 mappings.

> **Dodo token**
>
> The exchange gives a Dodo token that has the same format as an API key. Send it as `Authorization: Bearer <token>`. There is no refresh token. To renew the token, do the exchange again.

## How the Exchange Works

```mermaid theme={null}
sequenceDiagram
    participant W as Your workload (SDK)
    participant P as Your platform (OIDC issuer)
    participant D as Dodo Payments API

    W->>P: Request an OIDC token (audience: Dodo Payments)
    P-->>W: Signed JWT
    W->>D: POST /workload-identity/token (JWT + identity_provider_id)
    D->>P: Get the signing keys (JWKS), cached
    D->>D: Verify the JWT and find one matching mapping
    D-->>W: Dodo token with the mapping scopes
    W->>D: API calls with Authorization: Bearer <Dodo token>
```

1. The workload requests an OIDC token from its platform. The audience of the token is the Dodo Payments audience of your mode.
2. The workload sends the JWT and the identity provider id to `POST /workload-identity/token`. This request does not need an API key.
3. Dodo Payments verifies the JWT signature with the public keys of the issuer. It also checks the issuer, the audience, the times, and the required claims.
4. Dodo Payments finds the enabled mappings whose conditions all match the JWT. Exactly one mapping must match.
5. Dodo Payments gives back a Dodo token with the scopes of that mapping.

The SDKs do these steps for you. Refer to [SDK Usage](https://docs.dodopayments.com/developer-resources/workload-identity/sdk-usage.md).

### Audience

The default audience is the Dodo Payments URL of the mode of the provider:

| Mode | Audience |
| - | - |
| Live mode | `https://live.dodopayments.com` |
| Test mode | `https://test.dodopayments.com` |

Thus a test mode JWT fails in live mode. A provider cannot use the audience of the other mode. The `kubernetes`, `gcp`, `vercel`, and `custom` types must use the default audience. The `github_actions`, `gitlab`, `terraform_cloud`, and `aws` types can use a different audience.

### Verification Rules

| Check | Rule |
| - | - |
| Signature | RS256, RS384, RS512, PS256, ES256, ES384, or EdDSA. The `kid` header must select a key from the JWKS of the issuer. |
| `iss` | Must be equal to the issuer of the provider. |
| `aud` | Must contain the audience of the provider. `aud` can be a string or a list. |
| `sub` | Required. |
| `iat`, `exp` | Required, with 60 seconds of clock leeway. `nbf` is used when present. |
| JWT lifetime | `exp - iat` must be 1 hour or less. For `terraform_cloud` and `kubernetes`, 24 hours or less. |
| Mapping | Exactly one enabled mapping must match. Dodo Payments never combines the scopes of two mappings. |

## Token Lifetime

A Dodo token lives for the `token_ttl_seconds` of the mapping. The value is between 300 and 3600 seconds. The default is 3600 seconds (1 hour). The lifetime does not depend on the `exp` of the JWT.

The `terraform_cloud` type is different. HCP Terraform gives one JWT for each plan or apply phase, and that JWT can do one exchange only. Thus the Dodo token lives until the JWT expires, which is the end of the run phase, with a maximum of 24 hours.

A mapping can have a maximum of 500 active tokens. When it has 500, the exchange fails with the `token_limit` reason until tokens expire or you revoke them.

## Scopes

The Dodo token gets the scopes of the mapping that matched. The workload cannot ask for more scopes. Each scope of a mapping must be a scope that the dashboard user who creates the mapping also holds. Refer to [Dashboard Setup](https://docs.dodopayments.com/developer-resources/workload-identity/dashboard-setup.md#add-a-mapping).

## IP Allowlist

A mapping can have a list of allowed CIDR ranges (`allowed_cidrs`), for IPv4 and IPv6.

* **At the exchange.** If the source IP is not in the list, the exchange fails with `invalid_grant` and the `ip_not_allowed` reason.
* **On each API call.** The Dodo token gets a copy of the list. Each API call from an IP that is not in the list gets `403`.

Use this list to limit a workload to its known egress IPs, for example the NAT IPs of a cluster.

## Replay Protection

The `github_actions`, `gitlab`, `terraform_cloud`, and `aws` platforms give a new JWT for each request. For these types, the JWT must have a `jti` claim, and each JWT can do one exchange only. A second exchange with the same JWT fails with `invalid_subject_token` and the `replay` reason.

The `kubernetes`, `gcp`, `vercel`, and `custom` types do not enforce `jti`. For example, the kubelet writes the same service account token to a file for most of its life, and all processes in the pod read that token. For these types, these controls apply:

* The provider must use the Dodo Payments audience of its mode, thus no other service accepts the JWT.
* The mapping must pin a stable claim, for example `sub`.
* The Dodo token lives 1 hour or less.
* You can add `allowed_cidrs` to the mapping.

## Revocation

These actions stop Dodo tokens immediately:

| Action | Effect |
| - | - |
| Change or delete a mapping | Revokes all tokens of that mapping. |
| Change the trust of a provider (`audience`, `discovery_url`, or `uploaded_jwks`) | Revokes all tokens of all mappings of that provider. A change to the name only does not revoke tokens. |
| Delete a provider | Deletes its mappings and revokes their tokens. |
| Revoke from the dashboard | Revokes one token, or all tokens of a mapping. The mapping stays. |
| Self-revoke | The workload calls `POST /workload-identity/token/revoke` with its token. |

Dodo Payments examines the JWT claims at the exchange only. A change on your platform after the exchange does not revoke a Dodo token. Examples are a moved repository, a deleted service account, or a renamed environment. The token stays valid until it expires, and the next exchange fails. For an immediate stop, use one of the actions in the table.

To revoke the current token from a workload, send it to the revoke endpoint. The response is `204`:

```bash theme={null}
curl -X POST https://live.dodopayments.com/workload-identity/token/revoke \
  -H "Authorization: Bearer $DODO_TOKEN"
```

## Audit Events

The dashboard lists the events of your providers for 90 days.

| Event type | When |
| - | - |
| `exchange_succeeded` | An exchange gave a token. Records the mapping, the token prefix, the allowed JWT claims, the source IP, and the user agent. |
| `exchange_failed` | An exchange failed. Records the reason code, the source IP, and the user agent. Records the claims only when the signature is valid. |
| `token_revoked` | A token was revoked from the dashboard or by the workload. |
| `mapping_tokens_revoked` | All tokens of a mapping were revoked. |
| `provider_created`, `provider_updated`, `provider_deleted` | A dashboard user changed a provider. Records the user and the changed fields. |
| `mapping_created`, `mapping_updated`, `mapping_deleted` | A dashboard user changed a mapping. Records the user and the changed fields. |

Events record only an allowlist of claims, for example `iss`, `sub`, `jti`, `repository`, `ref`, `sha`, and `run_id`. Dodo Payments never stores the JWT.

### Reason Codes

| Reason code | Meaning |
| - | - |
| `invalid_signature` | The signature is not valid, or the algorithm is not permitted. |
| `unknown_kid` | No key in the JWKS of the issuer has the `kid` of the JWT. |
| `issuer_mismatch` | The `iss` claim is not the issuer of the provider. |
| `audience_mismatch` | The `aud` claim does not contain the audience of the provider. |
| `expired` | The JWT expired, is not valid yet, or has a lifetime that is too long. |
| `missing_claim` | A required claim or header is missing or has the wrong type. |
| `event_not_allowed` | The GitHub Actions event is `pull_request_target` or `workflow_run`. |
| `replay` | The JWT was used before (`jti`). |
| `no_mapping` | No enabled mapping matches the JWT. |
| `multiple_mappings` | More than one enabled mapping matches the JWT. |
| `ip_not_allowed` | The source IP is not in the `allowed_cidrs` of the mapping. |
| `jwks_unavailable` | Dodo Payments could not get the keys of the issuer. |
| `token_limit` | The mapping has 500 active tokens. |
| `rate_limited` | Too many failed exchanges in one minute. Dodo Payments recorded one summary event in place of the other failure events. |

### Event Limits

Dodo Payments records failure events only for a known provider. A provider records a maximum of 60 `exchange_failed` events each minute for one source IP, and 600 each minute for all source IPs. The first failure over a limit records one `rate_limited` event. These limits apply to the audit log only. They do not change the result of an exchange.

## Exchange Errors

The exchange endpoint uses the OAuth 2.0 error format `{ "error", "error_description" }`.

| `error` | HTTP status | When |
| - | - | - |
| `invalid_request` | 400 | A field is missing or not supported. |
| `invalid_subject_token` | 401 | Bad signature, unknown `kid`, wrong `iss` or `aud`, expired JWT, missing claim, or a JWT that was used before. |
| `invalid_grant` | 403 | Unknown provider, wrong mode, archived business, IP not allowed, or not exactly one matching mapping. |
| `temporarily_unavailable` | 503 | Dodo Payments could not get the discovery document or the JWKS. Try again. |

`error_description` tells you which claim or value to correct. It does not show the expected value of a condition. The audit event has the full reason.

> **[Dashboard Setup](https://docs.dodopayments.com/developer-resources/workload-identity/dashboard-setup.md)**
>
> Create your first provider and mapping.

> **[SDK Usage](https://docs.dodopayments.com/developer-resources/workload-identity/sdk-usage.md)**
>
> Use workload identity from the SDKs or with curl.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.