---
title: "OAuth 2.0"
description: "Authenticate Wazapin integrations with Authorization Code + PKCE or the Device Authorization flow."
---

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

# OAuth 2.0

Wazapin supports OAuth 2.0 for integrations that need delegated access instead of a long-lived API key. Use OAuth for a CLI, desktop app, or external application that acts on behalf of a Wazapin user.

For server-to-server integrations that you control, [API keys](/api/authentication) remain the simpler option.

:::info
OAuth tokens are sent as `Authorization: Bearer <access_token>`. API keys use `X-Api-Key: <api_key>` and are not interchangeable with OAuth tokens.
:::

## OAuth server

Use `https://api.wazapin.com` as the issuer:

```text
https://api.wazapin.com
```

Discover the current endpoints and supported capabilities programmatically:

```bash
curl -sS https://api.wazapin.com/.well-known/oauth-authorization-server
```

The discovery document currently advertises:

- Authorization Code with PKCE (`S256`)
- Refresh tokens
- Device Authorization Grant (RFC 8628)
- Token introspection (RFC 7662)
- Token revocation (RFC 7009)

## Register an OAuth client

OAuth clients must be registered before they can request authorization. Registration provides a `client_id`, allowed redirect URI, grant types, response types, and scopes.

Do not put a client secret in a browser, desktop app, CLI, or other public client. Public clients should use PKCE. Confidential clients may use a secret only from a protected server.

The `wazapin-docs` client exists for the hosted documentation integration. Its registered callback URLs are deployment-specific; do not copy it for a new application. Ask Wazapin to register your own client and exact callback URL before using Authorization Code in production.

## Device Authorization flow

Use Device Authorization for a CLI, SSH session, or another device that cannot safely open a callback URL. This is the recommended flow for headless integrations.

### 1. Request a device code

The device endpoint accepts JSON. `client_id` is required; `scope` is a space-delimited list.

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/device" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
"client_id": "YOUR_CLIENT_ID",
"scope": "messages:read messages:send channels:read"
  }'
```

A successful response contains:

```json
{
  "device_code": "DEVICE_CODE",
  "user_code": "ABCD-EFGH",
  "verification_uri": "https://app.wazapin.id/v1/oauth/device",
  "verification_uri_complete": "https://app.wazapin.id/v1/oauth/device?user_code=ABCD-EFGH",
  "expires_in": 600,
  "interval": 5
}
```

The verification URL and expiry are returned by the server. Always use the returned values rather than hardcoding them.

### 2. Ask the user to authorize the device

Open `verification_uri_complete` in a browser. The user signs in, selects the Wazapin organizations the device may access, and authorizes the device.

Show the user the `user_code` as a fallback when the complete URL cannot be opened.

### 3. Poll for the token

Poll the token endpoint no more often than the returned `interval`. The token endpoint uses the OAuth-standard form-encoded body, not JSON.

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Accept: application/json" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  --data-urlencode "device_code=DEVICE_CODE" \
  --data-urlencode "client_id=YOUR_CLIENT_ID"
```

While the user has not finished, the server returns an OAuth error such as:

```json
{
  "error": "authorization_pending"
}
```

Stop polling when the code expires or an unrecoverable error is returned. After approval, the response contains a bearer token:

```json
{
  "access_token": "wazapin_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "messages:read messages:send channels:read"
}
```

### 4. Call the API

```bash
curl -sS "https://api.wazapin.com/v1/channels" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

When a user authorizes more than one organization, send the organization identifier required by the target resource. Keep the access token in a secure local credential store; never print it in logs.

## Authorization Code + PKCE

Use Authorization Code with PKCE for web, desktop, and mobile applications that can open a browser and receive a redirect.

The authorization endpoint is:

```text
GET https://api.wazapin.com/v1/oauth/authorize
```

Generate a high-entropy `code_verifier`, derive its `S256` `code_challenge`, and keep the verifier locally until the token exchange. Redirect the user to a URL like this:

```text
https://api.wazapin.com/v1/oauth/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https%3A%2F%2Fyour-app.example%2Foauth%2Fcallback&
  scope=messages%3Aread%20messages%3Asend&
  state=RANDOM_STATE&
  code_challenge=BASE64URL_SHA256_CODE_VERIFIER&
  code_challenge_method=S256
```

Validate `state` when the browser returns to your callback. Then exchange the one-time authorization code:

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Accept: application/json" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "code=AUTHORIZATION_CODE" \
  --data-urlencode "redirect_uri=https://your-app.example/oauth/callback" \
  --data-urlencode "code_verifier=ORIGINAL_CODE_VERIFIER"
```

The `redirect_uri` must exactly match the URI registered for the client. The hosted docs domain is not a general-purpose OAuth callback; obtain a client registration for your own application before testing this flow.

## Refresh an access token

When a refresh token is issued for your client, exchange it at the same token endpoint:

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "refresh_token=REFRESH_TOKEN"
```

Treat refresh tokens as credentials. Store them securely and rotate or revoke them when a user disconnects the integration.

## Introspect a token

Use introspection from a trusted backend to check whether a bearer token is active:

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/introspect" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
"token": "ACCESS_TOKEN"
  }'
```

An active token returns `active: true` with token metadata. An unknown or expired token returns:

```json
{
  "active": false
}
```

Do not expose introspection responses to untrusted clients.

## Revoke a token

Revoke an access or refresh token when the user disconnects your integration:

```bash
curl -sS -X POST "https://api.wazapin.com/v1/oauth/revoke" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
"token": "ACCESS_OR_REFRESH_TOKEN",
"token_type_hint": "access_token"
  }'
```

The endpoint is idempotent for an already-revoked token. Discard the token locally after a successful revocation request.

## OAuth errors

OAuth errors are returned as JSON from the token endpoint. Common cases include:

| Error | Meaning | Client action |
| --- | --- | --- |
| `authorization_pending` | The device has not been approved yet. | Wait for `interval`, then poll again. |
| `expired_token` | The device code is no longer valid. | Start a new device flow. |
| `invalid_client` | The client ID or authentication is invalid. | Check client registration. |
| `invalid_grant` | The code, device code, or refresh token is invalid. | Do not retry the same credential indefinitely. |
| `invalid_request` | A required parameter is missing or malformed. | Fix the request before retrying. |
| `access_denied` | The user or authorization server denied access. | Tell the user and stop the flow. |

## Security checklist

- Use PKCE with `S256` for public clients.
- Validate `state` on every authorization callback.
- Use exact, pre-registered redirect URIs; never accept arbitrary callback URLs.
- Request the minimum scopes your integration needs.
- Keep access and refresh tokens out of source control, URLs, logs, and client-side analytics.
- Revoke tokens when the user disconnects the integration.

## Next steps

- [API authentication](/api/authentication)
- [API overview](/api/overview)
- [CLI overview](/cli/overview)
- [API Reference](/api-reference)

Source: https://docs.wazapin.id/api/oauth/index.mdx
