OAuth2 genérico no se actualiza

Hola a todos,

Estoy teniendo problemas al integrarme con la API de Jobber. Tengo autenticación inicial y algunos flujos de trabajo funcionales, pero todo se rompe en 1 hora cuando access_token expira.

Jobber no requiere alcances especiales según mis pruebas para devolver el token de actualización, pero por alguna razón no se está devolviendo/almacenando, o n8n no lo está manejando correctamente.

¿Alguna sugerencia?

aquí está mi información de solución de problemas pegada del problema que hice en GitHub

Aquí está su documentación - https://developer.getjobber.com/docs/building_your_app/app_authorization/

Puedo autorizar inicialmente, sin embargo después de la expiración de 1 hora obtengo el siguiente error

Salida
1 elemento
Autorización fallida: verifica tus credenciales
Tipo de contenido no compatible: text/plain; charset=utf-8
Detalles del error

De la solicitud HTTP
Código de error

401

Mensaje completo

Tipo de contenido no compatible: text/plain; charset=utf-8
Solicitud

{ “oculto”: “{\n “query”: “{ quotes(first: 10, filter: { status: converted}) { nodes { id quoteNumber notes(first: 5) { nodes { … on QuoteNote { message } } } } } }”\n}”, “headers”: { “content-type”: “application/json”, “x-jobber-graphql-version”: “2025-04-16”, “accept”: “application/json,text/html,application/xhtml+xml,application/xml,text/;q=0.9, image/;q=0.8, /;q=0.7”, “Authorization”: “oculto” }, “method”: “POST”, “uri”: “https://api.getjobber.com/api/graphql”, “gzip”: true, “rejectUnauthorized”: true, “followRedirect”: true, “resolveWithFullResponse”: true, “sendCredentialsOnCrossOriginRedirect”: false, “followAllRedirects”: true, “timeout”: 300000, “encoding”: null, “json”: false, “useStream”: true }
Otra información
Índice del elemento

0

Tipo de nodo

n8n-nodes-base.httpRequest

Versión del nodo

4.4 (Última)

Versión de n8n

2.20.6 (Auto-hospedado)

Hora

12/05/2026, 09:52:03

Rastreo de la pila

NodeApiError: Autorización fallida: verifica tus credenciales en ExecuteContext.execute (/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-nodes-base@file+packages+nodes-base_@aws-sdkaws-sdkaws-sdkaws-sdk+credential-providers@3.808.0_asn1.js@5_8da18263ca0574b0db58d4fefd8173ce/node_modules/n8n-nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts:825:16) en WorkflowExecute.executeNode (/usr/local/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+package@opent@openlemetry+core_@open@opentelemetrye@opentelemetryemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/src/execution-engine/workflow-execute.ts:1048:9) en WorkflowExecute.runNode (/usr/local/lib/@opentelemetrynpmode_modules/n8n/node_modules/.@opentelemet@opentelemetryynpm/n8n-co@opentelemetry@opentelemetry@file+packages+core_@opentelemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/@opentelemetryodulesrc/ex@opentelemetryode_modulescution-engine/workflow-execute.ts:1@opentelemetry39:11) en /@opentelemetrysr/local/lib/node_@opentelemetryodules/n8n/@opentelemetryode_modules/.pnpm/n8n-core@file+packages+core_@opentelemetry+api@@opentelemetry7.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/s@opentelemetryc/execution@opentelemetryengine/workflow-execute.ts:1687:@opentelemetry7 en /usr/l@opentelemetrycal/lib/node_modules/n8n/node_modules/.pnpm/n8n-core@file+packages+core_@opentelemetry+api@1.9.0_@opentelemetry+exporter-trace-otlp_2c2e1f47b69b34bef6f634a13cbf61d9/node_modules/n8n-core/src/execution-engine/workflow-execute.ts:2339:11
Mi entendimiento es que la respuesta 401 debería desencadenar una actualización de token, pero eso no parece estar sucediendo. Reiniciar el flujo de trabajo no ayuda tampoco.

Si voy a credenciales puedo presionar “reconectar” lo que permitirá que las ejecuciones posteriores del flujo de trabajo funcionen durante una hora.

Para reproducir
Configurar credencial de OAuth2 genérica con authorization_code contra un proveedor que rota tokens de actualización.
Ejecutar flujo de trabajo con éxito.
Esperar hasta la expiración del token de acceso.
Observar ciclo de actualización; después del ciclo posterior, la autenticación falla requiriendo reconectar.
Comportamiento esperado
n8n debería persistir y usar el último refresh_token rotado de la respuesta de actualización de token.
Los flujos de trabajo deberían continuar sin reconectar manualmente.

Información de depuración
Información de depuración
núcleo
n8nVersion: 2.20.6
plataforma: docker (auto-hospedado)
nodeJsVersion: 24.14.1
nodeEnv: production
database: sqlite
executionMode: regular
concurrency: -1
license: enterprise (production)
consumerId: 268c9581-f4d9-44ed-a7be-25c5c834d114
almacenamiento
success: all
error: all
progress: false
manual: true
binaryMode: filesystem
gestión
enabled: true
maxAge: 336 hours
maxCount: 10000 executions
cliente
userAgent: mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/148.0.0.0 safari/537.36
isTouchDevice: false
clúster
instanceCount: 1
versions: 2.20.6
instances:
instanceKey: 0cffc1ac-43d6-4469-85cf-116816732522, hostId: main-dec161f067e6, instanceType: main, instanceRole: leader, version: 2.20.6
checks:
check: hostid-clash, status: succeeded, warnings: -
check: lifecycle, status: succeeded, warnings: -
check: split-brain, status: succeeded, warnings: -
check: version-mismatch, status: succeeded, warnings: -
Generado en: 2026-05-12T17:00:18.159Z

Sistema Operativo
Ubuntu 22.04 LTS

Versión de n8n
2.20.6

Versión de Node.js
lo que está en la imagen: docker.n8n.io/n8nio/n8n

Base de datos
SQLite (predeterminado)

Modo de ejecución
main (predeterminado)

Alojamiento
auto-hospedado

¡Bienvenido @oxidation0917 a nuestra comunidad! Soy Jay y soy un creador verificado de n8n.

Este es un bug conocido con Generic OAuth2 - el token de refresco no se está almacenando ni utilizando correctamente en algunos casos. Un workaround práctico mientras esperas la corrección: en la credencial de Generic OAuth2, asegúrate de que “Authentication” esté configurado en “Body” (no Header) si Jobber lo soporta, y verifica que la URL del token sea correcta e incluya cualquier credencial de cliente requerida en el body. Si los access tokens de Jobber tienen corta duración (1 hora), también puedes crear un flujo de refresco manual: un workflow programado que se ejecute cada 55 minutos, llame al endpoint de token de Jobber a través de HTTP Request con grant_type: refresh_token, y actualice la credencial a través de la API REST de n8n. Esto te mantiene operativo mientras se resuelve el bug.

Hola @nguyenthieutoan Muchas gracias por revisar esto.

Actualmente estoy usando Body en la configuración de autenticación ya que es la única forma en que funcionaría. Cuando dices que verifiques que Jobber incluye las credenciales requeridas en el body, ¿te refieres a verificar el body de un curl?

Estaba pensando en el enfoque que sugieres, usando un trigger cron, pero no podía visualizarlo bien en mi cabeza ni en los docs. ¿Hay alguna forma de acceder a las credenciales almacenadas (específicamente refresh_token) en la solicitud http? Entiendo la parte de actualizar la credencial mediante API (tendré que averiguar la estructura), pero como la solicitud requiere el refresh_token me quedé atascado en cómo acceder a él.

Hola @oxidation0917, me alegra poder aclarar esto un poco más.

  1. Sobre “credenciales en el body”
    Cuando mencioné verificar que Jobber incluya las credenciales requeridas en el body, quise decir: compara lo que envías desde n8n con un ejemplo curl funcional de la documentación de Jobber o de tus propias pruebas. Por ejemplo, en una llamada típica de actualización OAuth2, el body a menudo necesita campos como:
  • grant_type=refresh_token

  • refresh_token=<tu_refresh_token>

  • client_id=<tu_client_id>

  • client_secret=<tu_client_secret>

Si tu ejemplo curl funciona pero el nodo HTTP Request de n8n falla, puedes reflejar el exacto mismo body y headers del curl en el nodo.

  1. Acceder al refresh_token almacenado
    Desafortunadamente, con el bug actual de Generic OAuth2, n8n no está exponiendo el refresh_token almacenado de una forma fácil dentro de los nodos regulares. Exactamente por eso la actualización automática está fallando. Así que en lugar de intentar “leer” el refresh token de la credencial en tiempo de ejecución, el workaround habitual es:
  • Almacena el refresh_token en algún lugar que puedas controlar, por ejemplo en:

    • Una variable de n8n (variable de entorno), o

    • Una base de datos/tabla separada, o

    • Un almacén de datos simple como PostgreSQL/Firestore/Notion, dependiendo de tu stack.

  • Luego tu workflow programado puede:

    • Leer el refresh_token almacenado

    • Llamar al endpoint de token de Jobber con grant_type=refresh_token

    • Actualizar el token de acceso en n8n a través de la API REST

  1. Esquema del flujo de actualización manual
    Muy aproximadamente, el workflow se vería algo así:
  • Nodo Cron: se ejecuta cada 55 minutos

  • (Opcional) Nodo para obtener el refresh_token almacenado más reciente de tu almacenamiento

  • Nodo HTTP Request:

    • Método: POST

    • URL: URL de token de Jobber

    • Auth: ninguno (porque envías todo en el body)

    • Body: grant_type=refresh_token, refresh_token=..., client_id, client_secret, etc.

  • Nodo HTTP Request (API de n8n):

    • Método: PATCH

    • URL: https://<tu-url-n8n>/rest/credentials/<credential-id>

    • Auth: usa tu autenticación de API de n8n

    • Body: actualiza el accessToken (y opcionalmente el refresh token si Jobber devuelve uno nuevo)

Si quieres, puedo elaborar un ejemplo JSON concreto tanto para la solicitud de token de Jobber como para el payload de actualización de credenciales de n8n, para que puedas conectarlos directamente en tu instancia.

Parece que n8n no está almacenando el refresh_token después del flujo OAuth inicial. Te sugiero que verifiques la respuesta completa del token de Jobber y confirmes que el token de actualización se está devolviendo y persistiendo. A veces los proveedores solo lo devuelven en la primera solicitud de autorización.

También podrías intentar forzar parámetros de acceso sin conexión / consentimiento en la configuración de OAuth. Ya que abriste un problema en GitHub, compartir la respuesta de token sin procesar (con los secretos eliminados) probablemente ayudaría a reducir el problema rápidamente.

@nguyenthieutoan ¡Gracias! Esto es excelente. Estoy en buen camino escribiendo un paso a paso para dejar constancia, pero me estoy encontrando con problemas al actualizar a través de la API.

Solución de problemas que no funcionó

Inicialmente intenté:

{
  "data": {
    "oauthTokenData": {
      "access_token": "access_token",
      "refresh_token": "refresh_token"
    }
  }
}

Pero recibo

{
  "message": "request.body.data does not match allOf schema [subschema 0] with 12 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"grantType\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"accessTokenUrl\",request.body.data does not match allOf schema [subschema 2] with 1 error[s]:,request.body.data requires property \"clientId\",request.body.data does not match allOf schema [subschema 3] with 1 error[s]:,request.body.data requires property \"clientSecret\",request.body.data does not match allOf schema [subschema 4] with 1 error[s]:,request.body.data requires property \"scope\",request.body.data does not match allOf schema [subschema 5] with 1 error[s]:,request.body.data requires property \"authentication\",request.body.data does not match allOf schema [subschema 1] with 2 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"serverUrl\",request.body.data does not match allOf schema [subschema 2] with 4 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"authUrl\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"authQueryParameters\",request.body.data does not match allOf schema [subschema 3] with 4 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"sendAdditionalBodyProperties\",request.body.data does not match allOf schema [subschema 1] with 1 error[s]:,request.body.data requires property \"additionalBodyProperties\",request.body.data does not match allOf schema [subschema 4] with 2 error[s]:,request.body.data does not match allOf schema [subschema 0] with 1 error[s]:,request.body.data requires property \"jwksUriNotice\""
}

Intenté proporcionárselos, y después de algunas iteraciones con Claude termino con esto:

{
  "data": {
    "grantType": "authorizationCode",
    "clientId": "clientId",
    "clientSecret": "clientSecret",
    "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
    "authUrl": "https://api.getjobber.com/api/oauth/authorize",
    "serverUrl": "{{MI HOST N8N?}}"
    "authQueryParameters": "",
    "scope": "",
    "authentication": "body",
    "jweEnabled": false,
    "oauthTokenData": {
      "access_token": "access_token",
      "refresh_token": "refresh_token",
      "token_type": "Bearer"
    }
  }
}

que aparentemente pasa las verificaciones del esquema, pero ahora recibo un error 500. No sé qué debería ir en serverURL así que puse mi host de n8n.

Código	Detalles
500
Sin documentar
Error: Internal Server Error

Cuerpo de la respuesta
Descargar

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Internal Server Error</pre>
</body>
</html>

Esta es mi estructura de credenciales de la base de datos que no tiene ninguno de los parámetros adicionales que exige la API. @David_Warner, parece que n8n está almacenando refresh_token.

{
    "authUrl": "https://api.getjobber.com/api/oauth/authorize",
    "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
    "clientId": "clientId",
    "clientSecret": "clientSecret",
    "scope": "offline_access",
    "authentication": "body",
    "oauthTokenData": {
        "access_token": "access_token",
        "refresh_token": "refresh_token",
        "token_type": "Bearer"
    }
}

Aguanta. Logré que la actualización por API funcionara después de jugar con Request_Body. Compartiré los detalles pronto.

Edición - Aquí está lo que he documentado hasta ahora.

Un gran agradecimiento a @nguyenthieutoan por encaminarme en la dirección correcta. He hecho mucho progreso.

Esperando hacer esto más fácil para alguien más (y para mí) en el futuro, pasé por todos los pasos de autenticación nuevamente, documentando mientras avanzaba.

Autenticación Inicial / Pruebas

  1. URL de Autorización - Ingresa en el navegador que está autenticado con Jobber. La URL a la que serás redirigido contiene el código de autorización y el estado como confirmación.
https://api.getjobber.com/api/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https:/YOUR_HOST/rest/oauth2-credential/callback&state=abc123

Respuesta que recibí:

https://n8n.lab.atreehuman.com/rest/oauth2-credential/callback?code=CODE&state=abc123
  1. Curl con código de autorización - Ajusta este CURL con tu code de la URL anterior
curl -X POST https://api.getjobber.com/api/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=CODE&redirect_uri=YOUR_HOST/rest/oauth2-credential/callback"

Respuesta:

{"access_token":"ACCESS_TOKEN","refresh_token":"REFRESH_TOKEN"}%

Ahora estás autenticado y tienes 1 hora para actualizar tu access_token antes de que expire. Tu refresh_token debería funcionar después de eso (indefinidamente hasta la rotación?). Por defecto, Jobber API rota refresh_token en cada actualización de tu access_token. Debes almacenar tu refresh_token en algún lugar accesible para tu actualización.

  1. Prueba de Autorización de API:
curl -X POST -H "Authorization: Bearer ACCESS_TOKEN" "https://api.getjobber.com/api/graphql"

Respuesta cuando estás autorizado:

{"message":"An API version must be specified"}%

Actualizando Credenciales de n8n

  1. Crea una clave de API en Configuración > n8n API con alcances de credential:list, credential_update y credential:read. Guarda la clave que se muestra en la ventana emergente después de ambos en tu portapapeles y en una ubicación segura. La necesitarás más tarde cuando crees tu flujo de trabajo cron para actualizar.

  2. Abre el Parque de Juegos de API y autoriza con tu clave de API. Encontré que tuve que actualizar la página después de la autenticación para que la autenticación surtiera efecto.

  3. Desplázate hacia abajo hasta GET /credentials haz clic y “Pruébalo”

  4. Encuentra tu credencial de Jobber en los datos de respuesta y guarda su id en tu bloc de notas y portapapeles

  5. Busca más abajo y encuentra PATCH /credentials/id, haz clic en “Pruébalo” y pega tu id.

  6. Dentro de “Request Body” pega lo siguiente y actualízalo con tus datos. (refresh_token realmente no necesita estar aquí ya que n8n no lo está usando)

{
    "data": {
        "grantType": "authorizationCode",
        "serverUrl": "",
        "jweEnabled": false,
        "clientId": "CLIENT_ID",
        "clientSecret": "CLIENT_SECRET",
        "accessTokenUrl": "https://api.getjobber.com/api/oauth/token",
        "authUrl": "https://api.getjobber.com/api/oauth/authorize",
        "scope": "",
        "authentication": "body",
        "authQueryParameters": "",
        "oauthTokenData": {
            "access_token": "ACCESS_TOKEN",
            "refresh_token": "REFRESH_TOKEN"
        }
    }
}

Deberías obtener un Código 200 con cuerpo de respuesta:

{
    "id": "ID",
    "name": "Jobber",
    "type": "oAuth2Api",
    "isManaged": false,
    "isGlobal": false,
    "isResolvable": false,
    "resolvableAllowFallback": false,
    "resolverId": null,
    "createdAt": "2026-05-09T17:53:07.738Z",
    "updatedAt": "2026-05-16T19:27:53.231Z"
}

Ahora todo lo que queda es mover tu refresh_token a algún lugar seguro que sea accesible desde un flujo de trabajo, y luego crear un flujo de trabajo que actualice el token y actualice la API.

Actualizando tu access_token

curl -X POST https://api.getjobber.com/api/oauth/token -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN"

Respuesta:

{"access_token":"ACCESS_TOKEN","refresh_token":"REFRESH_TOKEN"}%

Solo toma eso y actualiza tus credenciales con la API como arriba, y deberías estar listo. ¡Ahora todo esto solo necesita convertirse en un flujo de trabajo y se le aplique una solución temporal!

So, I’m still hoping to get this issue resolved, but for now we can workaround it.

Here’s a workflow I came up with. You’d have to set up a Postgres database / credentials and point those nodes at it as well as set an error reporting workflow, or disable that flag.

I don’t really like storing the credentials in the database, but currently it’s the most practical approach without a self-hosted enterprise license. Any suggestions otherwise that are more secure?

I’m open to any suggestions on the workflow too. Thanks all!

Editar - Desmarcar como solución.

Bueno, es una especie de solución. Hace que la autenticación dure más tiempo. Tal vez un día ahora, pero parece que n8n está actualizando el token, solo que en su propio cronograma. Esto termina rompiendo la solución temporal.

¿Es posible que esto también afecte a las credenciales de la API OAuth2 de GitHub, no solo a las credenciales OAuth2 genéricas? Estoy usando una credencial OAuth2 de GitHub junto con nodos de GitHub para obtener información. Recientemente he estado recibiendo un error 401:
{ "message": "Bad credentials", "documentation_url": "https://docs.github.com/rest", "status": "401" }

Puedo resolver esto manualmente haciendo clic en el botón «Reconectar» en la página de credenciales. Pero esta solución alternativa solo funciona durante algunas horas.

Tengo el mismo problema con un nodo de API de OAuth2 de MCP. No actualiza el token. ¿Hay alguna noticia sobre la corrección?

Dos cosas que hay que verificar y que a menudo se pasan por alto: primero, asegúrate de que el campo “Refresh URL” en la credencial Generic OAuth2 esté configurado explícitamente con el endpoint de tokens de tu proveedor - n8n no lo deducirá de la “Access Token URL” aunque suelen ser iguales. Segundo, verifica que tu solicitud de autorización inicial incluya access_type=offline (o prompt=consent para proveedores basados en Google) - sin esto, muchos proveedores no emiten un token de actualización en absoluto, así que n8n no tiene nada que usar cuando el token de acceso expira. Puedes añadir esos parámetros bajo “Auth URI Query Parameters” en la configuración de credenciales.

Gracias por la respuesta, @nguyenthieutoan. Sí, ya he probado ambas opciones antes, pero no se actualiza después de aproximadamente 1 hora.

Con respecto a la URL, sí, completé ambas: la URL de autenticación y la URL del token de acceso,

¿hay otros pasos de solución de problemas que pueda intentar?

Gracias,

Para Google específicamente, el token de actualización se emite solo una vez, en la primera autorización. Si autorizaste sin access_type=offline y prompt=consent en el campo “Auth URI Query Parameters”, Google no devolverá un token de actualización, y ninguna cantidad de ajuste de URL lo solucionará. Prueba esto: en tu credencial, en Auth URI Query Parameters añade access_type=offline y prompt=consent, luego revoca y reauoriza la credencial desde cero. Eso debería hacer que Google emita un nuevo token de actualización.

Gracias, parece que funcionó.

No sabía cómo enviar ambos, pero fue simple, "access_type=offline&prompt=consent"