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.
- Tu backend redirige a
GET /integration/oauth2/authorizecon unstatealeatorio. - El comercio inicia sesión en Chytapay y autoriza.
- La Auth API vuelve a tu
redirect_uriconcodeystate. - Tu backend valida el
state. - Tu backend manda
code,clientIdyclientSecretaPOST /integration/oauth2/token. - La Auth API responde el
idTokeny elrefreshTokendel comercio.
Antes de empezar
Todo del mismo ambiente, Sandbox o Producción:
- El
clientIdy elclientSecret, en el Portal, en Credenciales. ElclientSecretva solo de tu backend a la Auth API (/token,/refreshy/revoke). - Una
redirect_urihttpsregistrada en el Portal, en Configuración. - 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.
1Mandá 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_ |
Sí | code |
client_ |
Sí | El clientId de tu integración |
redirect_ |
Sí | Una redirect_ 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.
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.
2Recibí 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.
- Compará el
statede la query, que Chytapay devuelve sin cambios, con el de la sesión. Si falta o no coincide, cortá el flujo. - Borrá el
statede la sesión: vale para un solo intento. - Cambiá el
codepor tokens antes de que venza (10 minutos, un solo uso).
3Cambiá 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
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
{
"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. No existe en Producción.
4Confirmá 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
curl https://integration-api.test.chytapay.com.ar/me \
-H "Authorization: Bearer $ID_TOKEN"Respuesta
{
"merchantUserId": "3f8a1c52-7d4e-4b19-9a60-2c5e8d1f0b47",
"email": "[email protected]",
"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 |
|---|---|
merchant |
El identificador de la cuenta de comercio en Chytapay. Es estable: guardalo para reconocerla. |
email |
El mail de la cuenta de comercio. |
fantasy |
El nombre de fantasía del comercio. |
account |
El titular de la cuenta: la razón social si es persona jurídica, o nombre y apellido si es persona física. |
account |
El CUIT o CUIL del titular, sin guiones. |
integration. |
El nombre de tu integración, el que ve el comercio al autorizar. |
tenant |
La clave de la sede 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:
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 |
id |
1 hora (expiresIn: 3600) |
POST / |
refresh |
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:
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.
Desvinculá un comercio
En el Portal, en Cuentas de comercio, 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, 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_ |
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.
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.
Ver los errores de OAuth
| Dónde | HTTP | code |
Causa | Qué hacer |
|---|---|---|---|---|
/ |
400 | Invalid |
Falta client_, redirect_ o response_, o la redirect_ no es idéntica (no existe redirect_). |
Compará protocolo, dominio, path y barra final. |
/ |
401 | Invalid |
client_ de otro ambiente. |
Usá el de Credenciales del mismo ambiente. |
| Pantalla de autorización | 400 | Invalid |
"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 | Invalid |
Contraseña incorrecta. | Reintentar. |
/ |
400 | Invalid |
code vencido, usado o de otro clientId, o redirect distinta. |
Autorizar de nuevo con la misma redirect_. |
/, /, / |
401 | Invalid |
clientId o client incorrectos. |
Revisá Credenciales. |
/ |
500 | Integration |
No se completó la vinculación; el code ya se consumió. |
Reiniciá desde /. |
/ |
400 | Invalid |
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 id. |
|
| Integration API | 403 | Cuenta de comercio no vinculada o desvinculada. | Autorizar. | |
GET / |
403 | La cuenta de comercio no está vinculada a tu integración. El cuerpo es texto plano, no JSON. | Autorizar. |