Xero OAuth2 credential connection in n8n (self-hosted v1.113.3 & 1.114.4) - Error: Unsupported content type: text/html; charset=utf-8 when attempting

Describe the problem/error/question

Error: Unsupported content type: text/html; charset=utf-8 when attempting Xero OAuth2 credential connection in n8n (self-hosted v1.113.3).

Critical Finding: GitHub OAuth2 works perfectly, proving infrastructure is correct. Issue is specific to Xero integration.

Has anyone successfully connected Xero OAuth2 credentials in self-hosted n8n v1.113.3? If so, what specific configuration made it work?

Is there a known issue with the Xero OAuth2 credential implementation that requires a workaround?

Note: The fact that GitHub OAuth works perfectly rules out all infrastructure, proxy, DNS, and environment configuration issues. This is specifically a Xero integration problem.

n8n setup

Environment
Setup: Self-hosted n8n on DigitalOcean (Ubuntu)
n8n Version: 1.113.3
Initial Setup: Docker with Caddy reverse proxy
Final Setup: Docker with Cloudflare Tunnel

Everything We Tried (In Order)

1. Caddy Reverse Proxy Configuration

Attempted fixes:

  • Modified Caddyfile to add explicit header forwarding (header_up Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto)
  • Changed to minimal Caddy configuration (letting defaults handle headers)
  • Removed all manual header configurations
  • Multiple Caddy container restarts

Result: No change to error

2. n8n Environment Variables

Variables tested:

  • N8N_PROXY_HOPS=1 (to trust reverse proxy)
  • EXPRESS_TRUST_PROXY=true (alternative trust proxy method)
  • WEBHOOK_URL=https://XXXXXX.com/ (verified with trailing slash)
  • N8N_PROTOCOL=https
  • N8N_HOST=XXXXXX.com
  • Removed potentially conflicting variables (N8N_PORT, NODE_ENV)

Result: Environment correctly set but error persisted

3. Complete Infrastructure Migration to Cloudflare Tunnel

Steps taken:

  • Transferred domain DNS from GoDaddy to Cloudflare
  • Created Cloudflare Tunnel (cloudflared)
  • Changed port binding to 127.0.0.1:5678:5678 (localhost only)
  • Configured tunnel with proper credentials and config file
  • Verified 4 tunnel connections established successfully
  • Confirmed n8n accessible via tunnel

Result: Infrastructure working perfectly (GitHub OAuth succeeds), but Xero still fails with identical error

4. Xero Application Configuration

Attempts:

  • Verified redirect URI exact match: https://XXXXXX.com/rest/oauth2-credential/callback
  • Created completely new Xero app from scratch
  • Verified app type: Web app
  • Confirmed scopes match tutorial requirements
  • Generated new Client ID and Client Secret multiple times
  • Tested with brand new credentials

Result: Same error with both old and new Xero apps

5. Diagnostic Tests

Tests performed:

  • GitHub OAuth2 test: :white_check_mark: SUCCESS (proves OAuth infrastructure works)
  • n8n web interface access: :white_check_mark: Working
  • Docker container networking: :white_check_mark: Verified listening on port 5678
  • Cloudflare Tunnel status: :white_check_mark: Active with 4 registered connections
  • Localhost curl test: :white_check_mark: Returns n8n HTML correctly
  • Multiple browser tests: Incognito, different browsers
  • Live log monitoring during connection attempts

Key Evidence

  • GitHub OAuth works → Infrastructure is NOT the problem
  • Xero OAuth fails with brand new app → Not a corrupted app configuration
  • Error is identical across all infrastructure changes → Problem is in n8n’s Xero integration code or Xero’s API response handling

What is the error message (if any)?

Error: Unsupported content type: text/html; charset=utf-8
More details : HTML of the Xero homepage.
Failed to connect. The window can be closed now.

Node

{
“nodes”: [
{
“parameters”: {
“url”: “=https://api.xero.com/api.xro/2.0/Accounts”,
“authentication”: “genericCredentialType”,
“genericAuthType”: “oAuth2Api”,
“sendHeaders”: true,
“headerParameters”: {
“parameters”: [
{
“name”: “xero-tenant-id”,
“value”: “removed”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.2,
“position”: [
816,
-2368
],
“id”: “d306435a-024c-4069-84b3-fe88efe613ba”,
“name”: “Xero - Get Chart of Accounts1”,
“credentials”: {
“oAuth2Api”: {
“id”: “kmN7PKdcEzrOHQBF”,
“name”: “Unnamed credential”
}
}
}
],
“connections”: {
“Xero - Get Chart of Accounts1”: {
“main”: [

]
}
},
“pinData”: {},
“meta”: {
“templateCredsSetupCompleted”: true,
“instanceId”: “5db59f632211c3294bc850ecaf66a632b1d2a9cd423b9f3ee02d96064b530e10”
}
}

Limited scope workaround: Switching from Authentication: Generic Credential Type, Generic Auth Type: OAuth2 API to Credential Type: Xero OAuth2 API

2 Likes

Another issue arises, the accounting.attachments scope is missing from the Xero OAuth.

@Jon any suggestions / update on this?