n8n Slack invalid_auth 케이스 스터디: 토큰이 맞아도 실패하는 이유
n8n 워크플로우를 짜다 하루를 태운 사례를 정리한다.
증상은 단순했다.
- GitHub PR merge → Claude → Notion + Slack 파이프라인
- Notion 은 성공, Slack 만 계속 invalid_auth 실패
- Slack Bot Token 은 방금 재발급 → "키값은 맞아"
이 상황에서 원인을 찾아간 순서를, 재현 가능한 형태로 남긴다. 같은 증상을 만나면 이 순서대로 좁혀 가면 30분 안에 잡을 수 있다.
첫 번째 착각: "토큰이 맞으니 API 문제겠지"
Slack API 가 invalid_auth 를 뱉는다고 하면 반사적으로 토큰을 의심한다.
그리고 토큰을 재발급받는다. 그런데 여전히 invalid_auth.
여기서 다들 "왜?"라며 삽질을 시작한다.
원인의 가짓수는 사실 유한하다.
1. Slack Bot Token 자체가 유효하지 않음 (rotate, revoke, uninstall)
2. 필요한 스코프가 없음 (chat:write 등)
3. HTTP 요청 방식이 잘못됨 (Content-Type 누락 등)
4. n8n 이 참조하는 credential 이 다른 것 (배선 문제)
5. Header 값에 Bearer prefix 누락 or 공백/개행 포함
이걸 위에서 아래로 하나씩 잘라내면 된다.
Step 1. 토큰 자체가 살아있는지 확인 — auth.test
n8n 밖으로 나와서 순수 curl 로 확인한다. 가장 스코프가 낮은 auth.test 부터.
curl -X POST https://slack.com/api/auth.test \
-H "Authorization: Bearer xoxb-..."응답 해석표:
{"ok":true, ...} → 토큰 살아있음, 다음 스텝
{"ok":false,"error":"invalid_auth"} → 토큰 자체 문제, rotate 필요
{"ok":false,"error":"token_revoked"} → workspace 재설치 필요
{"ok":false,"error":"account_inactive"} → workspace 자체 문제
내 케이스는 ok:true 였다. 즉 토큰은 완전히 유효하다는 게 확정.
여기서 원인 후보 1번은 제거된다.
Step 2. 실제 호출 API 로 확인 — chat.postMessage
auth.test 는 스코프가 없어도 통과한다.
n8n 이 실제 호출하는 chat.postMessage 는 chat:write 스코프와 채널 접근이 있어야 통과한다.
curl -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer xoxb-..." \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"channel":"#dev-log","text":"curl test"}'응답 해석표:
{"ok":true,...} → Slack 쪽 100% 정상
{"ok":false,"error":"not_in_channel"} → 봇을 채널에 /invite 필요
{"ok":false,"error":"missing_scope","needed":"chat:write"}
→ 스코프 추가 + 앱 재설치
{"ok":false,"error":"channel_not_found"} → 채널 ID 로 교체
{"ok":false,"error":"invalid_auth"} → 여기서 나오면 스코프 문제
내 케이스는 이것도 ok:true 였다. 실제 채널에 "curl test" 메시지가 도착했다.
이 시점에서 Slack 쪽은 100% 정상이라는 게 증명됐다. 그러면 원인은 오직 하나로 좁혀진다.
n8n 노드가 이 정상 토큰을 그대로 못 쓰고 있다.
Step 3. n8n credential 배선을 의심한다
n8n Credentials 목록을 다시 봤다.
- Slack (Header Auth) ← 방금 만든 것
- Header Auth account ← 이름부터 수상함 (n8n 기본 이름)
- Anthropic (Header Auth)
- GitHub PAT credential
- GitHub account
"Header Auth account" 라는 기본 이름 credential 이 남아 있었다. 이건 처음 노드를 만들 때 자동 생성된 이후 그대로 방치된 것.
여기서 가설이 서게 된다.
Slack: notify 노드가
Slack (Header Auth)가 아니라 옛날의Header Auth account를 참조하고 있는 게 아닐까?
Step 4. 노드의 credential 참조를 강제 재바인딩
n8n 은 노드가 credential 을 이름이 아니라 UUID 로 참조한다. credential 을 삭제/재생성하면 UUID 가 바뀌고, 노드는 옛날 UUID 를 그대로 붙들고 있는다.
해결 절차:
1. Slack: notify 노드 더블클릭
2. Credential for Header Auth 드롭다운 열기
→ 여기서 지금 뭐가 선택돼 있는지 눈으로 확인 (범인 확인)
3. 아무 다른 credential 하나 선택 → 다시 원래 원하는 것 선택
4. 노드 Save → 워크플로우 Save
5. 노드 우측 상단 Execute node
이 순간 파이프라인이 통과했다. 원인 확정.
회고: 이 삽질에서 얻은 교훈 3가지
1. "토큰이 맞다"는 곧바로 믿지 않는다
토큰이 물리적으로 맞아도 어디에 어떻게 저장돼 있는지가 다를 수 있다. n8n credential 필드에 저장된 값과 curl 에 붙여넣은 값이 정말 같은지는 직접 확인 전엔 모른다. (Value 필드가 마스킹돼 있어서 눈으로 못 봄)
2. 문제를 두 개로 자른다 — "API 는 되는가" vs "내 도구는 되는가"
Slack invalid_auth 를 만나면 곧바로 두 갈래로 자른다.
- curl 로도 실패한다 → Slack 자체 문제 (스코프, 채널, 토큰)
- curl 은 되는데 도구만 실패 → 내 도구의 배선 문제 (n8n credential 등)
curl 한 줄로 5분이면 두 영역을 분리할 수 있다. 이걸 안 하고 도구 UI 안에서만 뒤지면 하루가 간다.
3. UI 도구의 "기본 이름" credential 은 즉시 이름을 붙인다
Header Auth account, HTTP Request account 같은 자동 생성 이름은
반드시 그 자리에서 Slack (Header Auth), Notion (Header Auth) 처럼
용도 붙여서 리네임한다. 안 그러면 나중에 어느 노드가 뭘 참조하는지 알 수 없고,
credential 을 재생성할 때마다 배선이 꼬인다.
요약: invalid_auth 진단 체크리스트
3분 안에 원인 좁히는 순서.
[1] curl -X POST https://slack.com/api/auth.test -H "Authorization: Bearer $TOKEN"
→ ok:false 면 토큰 문제, rotate
[2] curl -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"channel":"#test","text":"ok"}'
→ not_in_channel : /invite 필요
→ missing_scope : 앱 스코프 추가 + 재설치
→ channel_not_found: 채널 ID 로 교체
[3] 위 둘 다 ok:true 인데 n8n 만 실패
→ 노드의 credential 재선택 후 Save → Execute node
Slack invalid_auth 뿐 아니라 Notion / OpenAI / 다른 SaaS 도 로직은 같다.
"API 는 되는가 → 내 도구는 되는가" 두 계층으로 잘라서 좁혀 들어가는 게 핵심.