> Chytapay es una plataforma de cobros por transferencia en Argentina. Esta página es parte de la doc de la Integration API: https://chytapay.com.ar/api/
> Conciliación recomendada: por CUIL. Ambientes: test (https://integration-api.test.chytapay.com.ar) y producción (https://integration-api.chytapay.com.ar).
> Página: "Conectar comercios" (https://chytapay.com.ar/api/conectar-comercios/), actualizada el 2026-10-09.
> Índice completo para asistentes de IA: https://chytapay.com.ar/llms.txt

# Conectar comercios

Cómo una cuenta de comercio autoriza a tu integración con OAuth, y cómo usar, renovar y revocar sus tokens.

Al terminar tenés el `idToken` de cada comercio vinculado, para crear cobros a su nombre, y sabés renovarlo, revocarlo y desvincular.

El flujo OAuth entre tu backend, el navegador del comercio y la Auth API.

1. Tu backend redirige a `GET /integration/oauth2/authorize` con un `state` aleatorio.
2. El comercio inicia sesión en Chytapay y autoriza.
3. La Auth API vuelve a tu `redirect_uri` con `code` y `state`.
4. Tu backend valida el `state`.
5. Tu backend manda `code`, `clientId` y `clientSecret` a `POST /integration/oauth2/token`.
6. La Auth API responde el `idToken` y el `refreshToken` del comercio.

## Antes de empezar

Todo del mismo ambiente, Sandbox o Producción:

- El `clientId` y el `clientSecret`, en el Portal, en [**Credenciales**](https://chytapay.com.ar/portal/credentials). El `clientSecret` va solo de tu backend a la Auth API (`/token`, `/refresh` y `/revoke`).
- Una `redirect_uri` `https` registrada en el Portal, en [**Configuración**](https://chytapay.com.ar/portal/settings).
- Una cuenta de comercio: la que recibe los pagos, no tu usuario de admin de la integración.

El flujo corre contra la Auth API, en `/integration/oauth2/`: `https://auth-api.test.chytapay.com.ar` en Sandbox y `https://auth-api.chytapay.com.ar` en Producción.



## 1. Mandá al comercio a autorizar



En tu backend, generá un `state` aleatorio, guardalo en la sesión y redirigí el navegador a `GET /integration/oauth2/authorize`:

| Parámetro       | Obligatorio            | Valor                                   |
| --------------- | ---------------------- | --------------------------------------- |
| `response_type` | Sí                     | `code`                                  |
| `client_id`     | Sí                     | El `clientId` de tu integración         |
| `redirect_uri`  | Sí                     | Una `redirect_uri` registrada, idéntica |
| `state`         | No, pero usalo siempre | Aleatorio, uno por intento              |
| `scope`         | No                     | Por defecto, `openid email profile`     |

También se aceptan los nombres en camelCase (`clientId`, `redirectUri`, `responseType`), pero en código nuevo usá snake_case.

```js
import crypto from 'node:crypto';

const AUTH_API = 'https://auth-api.test.chytapay.com.ar';
const REDIRECT_URI = 'https://tuapp.com/oauth/callback';

// Handler de Express: el comercio hace clic en "Conectar con Chytapay".
app.get('/conectar-chytapay', (req, res) => {
  const state = crypto.randomBytes(32).toString('base64url');
  req.session.chytapayState = state;

  const url = new URL(`${AUTH_API}/integration/oauth2/authorize`);
  url.search = new URLSearchParams({
    response_type: 'code',
    client_id: process.env.CHYTAPAY_CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    state,
  }).toString();

  res.redirect(url.toString());
});
```

La pantalla de autorización muestra el nombre y el logo de tu integración, y la comisión si tenés una oferta activa. **Ahí inicia sesión la cuenta de comercio, nunca el admin de la integración.** Usa `POST /integration/oauth2/complete-login`, que tu integración no llama.

## 2. Recibí el code y validá el state

Cuando el comercio autoriza, Chytapay redirige su navegador a tu `redirect_uri`:

```
https://tuapp.com/oauth/callback?code=<code>&state=<el state que mandaste>
```

El `state` evita que un atacante vincule a tu usuario con una cuenta ajena.

1. Compará el `state` de la query, que Chytapay devuelve sin cambios, con el de la sesión. Si falta o no coincide, cortá el flujo.
2. Borrá el `state` de la sesión: vale para un solo intento.
3. Cambiá el `code` por tokens antes de que venza ([**10 minutos**, un solo uso](https://chytapay.com.ar/api/conectar-comercios/#vida-tokens)).



## 3. Cambiá el code por tokens



Desde tu backend, `POST /integration/oauth2/token` con `grantType: "authorization_code"`, `code`, `clientId`, `clientSecret` y la misma `redirectUri` de `/authorize`:

**cURL**

```bash
curl -X POST https://auth-api.test.chytapay.com.ar/integration/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grantType": "authorization_code",
    "code": "'"$CODE"'",
    "clientId": "'"$CHYTAPAY_CLIENT_ID"'",
    "clientSecret": "'"$CHYTAPAY_CLIENT_SECRET"'",
    "redirectUri": "https://tuapp.com/oauth/callback"
  }'
```

**Respuesta**

```json
{
  "idToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIn0...",
  "tokenType": "Bearer",
  "expiresIn": 3600
}
```

El comercio queda vinculado. Guardá su `refreshToken`: te deja operar sin que autorice de nuevo. Si vuelve a autorizar, recibís tokens nuevos.

> **Nota:** En Sandbox, podés saltear OAuth con [un comercio de prueba](https://chytapay.com.ar/api/getting-started/#testing). No existe en Producción.

## 4. Confirmá qué comercio autorizó

Con el `idToken` recién obtenido, llamá a `GET /me` en la Integration API para saber a qué cuenta de comercio pertenece, y guardá su `merchantUserId` junto al vínculo en tu sistema:

**cURL**

```bash
curl https://integration-api.test.chytapay.com.ar/me \
  -H "Authorization: Bearer $ID_TOKEN"
```

**Respuesta**

```json
{
  "merchantUserId": "3f8a1c52-7d4e-4b19-9a60-2c5e8d1f0b47",
  "email": "contacto@papeleria-ejemplo.com.ar",
  "fantasyName": "Papelería del Sur",
  "accountHolderName": "Del Sur Papelería S.R.L.",
  "accountHolderTaxId": "30123456781",
  "integration": { "name": "Mi integración" },
  "tenantKey": "sede-centro",
  "linkedAt": "2026-05-20T14:30:00.000Z"
}
```

| Campo                | Qué es                                                                                                                                               |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `merchantUserId`     | El identificador de la cuenta de comercio en Chytapay. Es estable: guardalo para reconocerla.                                                        |
| `email`              | El mail de la cuenta de comercio.                                                                                                                    |
| `fantasyName`        | El nombre de fantasía del comercio.                                                                                                                  |
| `accountHolderName`  | El titular de la cuenta: la razón social si es persona jurídica, o nombre y apellido si es persona física.                                           |
| `accountHolderTaxId` | El CUIT o CUIL del titular, sin guiones.                                                                                                             |
| `integration.name`   | El nombre de tu integración, el que ve el comercio al autorizar.                                                                                     |
| `tenantKey`          | La clave de la [sede](https://chytapay.com.ar/api/sedes/) a la que está vinculada la cuenta. **No viene** si la cuenta no está vinculada a una sede. |
| `linkedAt`           | La fecha, en ISO 8601 UTC, de la **primera** autorización. Si el comercio se desvincula y autoriza de nuevo, no cambia.                              |

La identidad sale del `idToken`: `GET /me` no recibe parámetros y ignora los que le mandes. Una cuenta de comercio no vinculada o desvinculada recibe **403**.

## Autenticá las llamadas a la API

Cada request a la Integration API lleva el `idToken` en `Authorization`:

```bash
curl https://integration-api.test.chytapay.com.ar/payment-request/factura-1001 \
  -H "Authorization: Bearer $ID_TOKEN"
```

Su claim `aud` es tu `clientId` y su `sub`, la cuenta de comercio: así sabe Chytapay a nombre de quién operás. `/token` no devuelve access token: usás el `idToken`. El token del admin de la integración da **401**, y una cuenta de comercio no vinculada, **403**.



| Token          | Dura                       | Cómo se reemplaza                  |
| -------------- | -------------------------- | ---------------------------------- |
| `code`         | 10 minutos, un solo uso    | El comercio autoriza de nuevo      |
| `idToken`      | 1 hora (`expiresIn: 3600`) | `POST /integration/oauth2/refresh` |
| `refreshToken` | 180 días                   | El comercio autoriza de nuevo      |





Antes de que venza el `idToken`, o ante un 401, pedí uno nuevo con `POST /integration/oauth2/refresh`: `grantType: "refresh_token"`, el `refreshToken` del comercio, `clientId` y `clientSecret`. El comercio no interviene. La respuesta trae solo un `idToken` nuevo: el refresh token **no rota**, seguí usando el mismo hasta que lo revoques o venza.

## Revocá un refresh token

Cuando dejás de operar para un comercio, revocá su refresh token con `POST /integration/oauth2/revoke`:

```bash
curl -X POST https://auth-api.test.chytapay.com.ar/integration/oauth2/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "'"$REFRESH_TOKEN"'",
    "tokenTypeHint": "refresh_token",
    "clientId": "'"$CHYTAPAY_CLIENT_ID"'",
    "clientSecret": "'"$CHYTAPAY_CLIENT_SECRET"'"
  }'
```

Responde 200 sin cuerpo. `tokenTypeHint` es opcional y vale `refresh_token`. Revocar **no desvincula**: el comercio puede volver a autorizar. Para cortar el vínculo, [desvinculalo](https://chytapay.com.ar/api/conectar-comercios/#desvincular).

## Desvinculá un comercio

En el Portal, en [**Cuentas de comercio**](https://chytapay.com.ar/portal/users), usá **Desvincular** en la fila del comercio. Desde ahí, sus tokens reciben **403** en la Integration API hasta que el comercio autorice de nuevo. Si la cuenta ya estaba desvinculada, el Portal avisa que no está vinculada.

> **Atención:** **Desvincular no cancela los cobros pendientes.** Se pueden seguir pagando y la plata llega al comercio, pero tu integración deja de verlos. Si no querés que se paguen, cancelalos antes con `POST /payment-request/{referenceId}/cancel`.

## Mostrá tu logo en la pantalla de autorización

Sin logo, el comercio ve solo el nombre de tu integración. Subilo en el Portal, en [**Configuración**](https://chytapay.com.ar/portal/settings), sección **Logo**. Acepta PNG, JPEG o WEBP de hasta 5 MB (no SVG), lo reduce a 512 px del lado más largo y cada subida reemplaza la anterior.

## La redirect_uri no es la URL de webhook

|             | `redirect_uri`                                | URL de webhook                      |
| ----------- | --------------------------------------------- | ----------------------------------- |
| Para qué    | Recibir el `code` cuando un comercio autoriza | Recibir los eventos de pago         |
| Quién llega | El navegador del comercio, con un GET         | El backend de Chytapay, con un POST |
| Cuándo      | Una vez por autorización                      | Cada vez que pasa algo con un cobro |

Configurá la URL de webhook en [Webhooks](https://chytapay.com.ar/api/webhooks/#configurar-urls).

## Errores

Los errores traen `code`, `message` e `id`, que sirve para rastrear el caso. Los demás errores de la API están en [Operación y errores](https://chytapay.com.ar/api/operacion/).

| Dónde                           | HTTP | `code`                    | Causa                                                                                                                        | Qué hacer                                                                                    |
| ------------------------------- | ---- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `/authorize`                    | 400  | `InvalidRequestError`     | Falta `client_id`, `redirect_uri` o `response_type`, o la `redirect_uri` no es idéntica (no existe `redirect_uri_mismatch`). | Compará protocolo, dominio, path y barra final.                                              |
| `/authorize`                    | 401  | `InvalidClientError`      | `client_id` de otro ambiente.                                                                                                | Usá el de [**Credenciales**](https://chytapay.com.ar/portal/credentials) del mismo ambiente. |
| Pantalla de autorización        | 400  | `InvalidGrantError`       | "No encontramos una cuenta con ese email.": entró el admin o una cuenta de otro ambiente.                                    | Que autorice la cuenta de comercio.                                                          |
| Pantalla de autorización        | 401  | `InvalidCredentialsError` | Contraseña incorrecta.                                                                                                       | Reintentar.                                                                                  |
| `/token`                        | 400  | `InvalidGrantError`       | `code` vencido, usado o de otro `clientId`, o `redirectUri` distinta.                                                        | Autorizar de nuevo con la misma `redirect_uri`.                                              |
| `/token`, `/refresh`, `/revoke` | 401  | `InvalidClientError`      | `clientId` o `clientSecret` incorrectos.                                                                                     | Revisá [**Credenciales**](https://chytapay.com.ar/portal/credentials).                       |
| `/token`                        | 500  | `IntegrationSetupError`   | No se completó la vinculación; el `code` ya se consumió.                                                                     | Reiniciá desde `/authorize`.                                                                 |
| `/refresh`                      | 400  | `InvalidGrantError`       | Refresh token revocado o inválido.                                                                                           | Autorizar de nuevo.                                                                          |
| Integration API                 | 401  |                           | Token ausente, vencido o que no es de una cuenta de comercio.                                                                | Renová el `idToken`.                                                                         |
| Integration API                 | 403  |                           | Cuenta de comercio no vinculada o desvinculada.                                                                              | Autorizar.                                                                                   |
| `GET /me`                       | 403  |                           | La cuenta de comercio no está vinculada a tu integración. El cuerpo es texto plano, no JSON.                                 | Autorizar.                                                                                   |

- [Crear cobros](https://chytapay.com.ar/api/crear-cobros/): Cobros a nombre del comercio.
- [Webhooks](https://chytapay.com.ar/api/webhooks/): URL de webhook y firma.
- [Cómo funciona](https://chytapay.com.ar/api/concepts/): Actores y vinculación.
- [Operación y errores](https://chytapay.com.ar/api/operacion/): Límites y errores por endpoint.
