n8n은 보통 워크플로우 자동화 도구로 소개된다.
직접 써보면 그보다는 이벤트 처리 런타임이자 경량 API 컴포저에 가깝다.
코드 한 줄 없이 Webhook을 열고, 외부 API를 호출하고, 응답을 가공해서 돌려줄 수 있다.
같은 영역의 Zapier나 Make와 갈리는 지점은 셀프호스팅이다.
둘은 유료 SaaS라 데이터가 외부 서버를 거치고 노드 개수에도 제한이 있지만, 오픈소스인 n8n은 Docker로 내 서버에 띄우면 데이터가 밖으로 나가지 않고 그런 제한도 없다.
복잡한 로직이 필요하면 Code 노드에서 JavaScript로 직접 처리할 수 있다.
Docker Compose로 로컬에 n8n을 띄우고, Basic Auth가 걸린 Webhook API를 하나 만든다.
완성하면 이런 흐름이 된다.
1. Docker Compose로 n8n 실행하기
n8n은 Docker 이미지로 제공된다.
별도 DB 없이 SQLite만으로도 충분히 돌아간다.
docker-compose.yml
services:
n8n:
image: n8nio/n8n:latest
ports:
- "5678:5678"
environment:
TZ: Asia/Seoul
N8N_HOST: localhost
N8N_PORT: 5678
N8N_PROTOCOL: http
WEBHOOK_URL: http://localhost:5678
N8N_ENCRYPTION_KEY: your-secret-key
volumes:
- ./n8n_data:/home/node/.n8nPostgres 대신 SQLite를 쓰는 이유는 단순하다.
로컬 테스트 용도라면 SQLite로 충분하고, 워크플로우 데이터는 n8n_data 볼륨에 저장되므로 나중에 프로덕션으로 옮길 때 DB만 교체하면 된다.
실행
docker compose up -dhttp://localhost:5678 접속하면 n8n 대시보드가 뜬다.

계정을 생성하고 로그인하면 이런 대시보드가 보인다.
여기서 워크플로우를 만들고 관리한다.
2. 완성된 워크플로우 미리보기
이번에 만들 워크플로우는 이렇게 생겼다.

노드 4개짜리 간단한 구조다.
Webhook이 외부 요청을 받는 진입점이고, HTTP Request가 받은 데이터를 httpbin.org로 전달한다.
Edit Fields(Set)가 응답을 가공하면, Respond to Webhook이 클라이언트에게 결과를 돌려준다.
각 노드를 차례로 설정한다.
3. Webhook 노드 설정
Webhook 노드는 트리거이면서 API 엔드포인트다.
이 노드로 n8n을 API 엔드포인트로 사용할 수 있다.

주요 설정
| 항목 | 값 | 설명 |
|---|---|---|
| HTTP Method | POST | 데이터를 받으므로 POST |
| Path | webhook | 엔드포인트 경로 |
| Authentication | Basic Auth | 간단한 인증 |
| Respond | Using 'Respond to Webhook' Node | 가공 후 응답 |
여기서 중요한 것은 Respond 설정이다.
Immediately: Webhook이 받자마자 바로 응답Using 'Respond to Webhook' Node: 뒤에서 가공한 후 응답
httpbin 응답을 가공해서 돌려줘야 하므로 후자를 선택한다.
4. Basic Auth 인증 테스트
별도 인증 서버 없이 Webhook 노드 자체에 Basic Auth를 걸 수 있다.

왼쪽은 인증 없이 호출해 401 Unauthorized를 받았고, 오른쪽은 -u test:test로 인증해 정상 응답을 받았다.
# 인증 없이 (실패)
curl -X POST http://localhost:5678/webhook-test/webhook
# 인증 포함 (성공)
curl -X POST http://localhost:5678/webhook-test/webhook \
-u test:test \
-H "Content-Type: application/json" \
-d '{"event": "test"}'5. HTTP Request 노드
Webhook으로 받은 body를 그대로 외부 API에 전달한다.
테스트용으로 httpbin.org를 쓴다.

핵심 설정
| 항목 | 값 |
|---|---|
| Method | POST |
| URL | https://httpbin.org/post |
| Body Content Type | JSON |
| Body | ={{ $json.body }} |
핵심은 ={{ $json.body }} 표현식이다.
"이전 노드의 JSON에서 body 필드를 가져와라"는 의미다.
n8n에서 노드 간 데이터 전달은 이런 식으로 한다.
={{ }}와 {{ }}의 차이
처음 n8n을 사용할 때 혼동하기 쉬운 문법이다.
| 문법 | 용도 | 예시 |
|---|---|---|
={{ expression }} | 필드 값 전체가 표현식일 때 | ={{ $json.body }} |
{{ expression }} | 문자열 안에 표현식을 삽입할 때 | Hello, {{ $json.name }}! |
표현식은 다음과 같이 쓴다.
// 필드 값 전체가 동적일 때
Body: ={{ $json.body }}
// 문자열 중간에 값 삽입
Message: "User {{ $json.userId }} logged in at {{ $json.timestamp }}"
// URL에 파라미터 삽입
URL: https://api.example.com/users/{{ $json.id }}= 기호는 이 필드 전체가 표현식이라는 표시다.
없으면 문자열 템플릿으로 처리된다.
스크린샷을 보면 Webhook에서 받은 데이터가 HTTP Request 노드로 잘 전달된 걸 확인할 수 있다.
6. Edit Fields (Set) 노드
HTTP Request의 응답을 그대로 돌려줘도 되지만, 보통은 가공이 필요하다.

설정 예시
| 필드 | 값 | 설명 |
|---|---|---|
tracked | true | 고정값 |
echo | ={{ $json.json }} | httpbin이 돌려준 JSON |
응답 스키마를 명시적으로 정의하는 단계다.
클라이언트가 받을 JSON 구조는 이렇다.
{
"tracked": true,
"echo": { ... }
}7. Respond to Webhook 노드
마지막 노드다.
Edit Fields에서 가공한 데이터를 클라이언트에게 응답으로 보낸다.

설정
| 항목 | 값 |
|---|---|
| Respond With | All Incoming Items |
이전 노드에서 만든 데이터를 그대로 응답으로 보내면 된다.
주의할 점이 하나 있다.
이 노드는 "Execute Step" 버튼으로는 테스트가 안 된다.
Webhook이 실제 HTTP 요청을 받아야 전체 플로우가 실행되기 때문이다.
8. 전체 워크플로우 테스트
설정이 끝나면 전체 워크플로우를 실행한다.

각 노드에 초록색 체크가 뜨면 성공이다.
Webhook이 요청을 받고, HTTP Request가 httpbin에 전달하고, Edit Fields가 응답을 가공한 뒤, Respond to Webhook이 클라이언트에게 반환하는 흐름을 왼쪽에서 오른쪽으로 확인할 수 있다.
9. curl로 최종 테스트
curl로 호출해서 응답을 확인해보자.

curl -X POST http://localhost:5678/webhook-test/webhook \
-u test:test \
-H "Content-Type: application/json" \
-d '{"userId": "user123", "event": "page_view"}'응답:
{
"tracked": true,
"echo": {
"userId": "user123",
"event": "page_view"
}
}프로덕션 URL
테스트가 끝나면 워크플로우를 Active 상태로 바꾼다.
그러면 URL이 바뀐다.
테스트: http://localhost:5678/webhook-test/webhook
운영: http://localhost:5678/webhook/webhook경로의 webhook-test가 webhook으로 바뀐다.
정리
Webhook으로 엔드포인트를 열고, 노드 단위로 인증을 걸고, 코드 없이 외부 API 호출과 응답 가공까지 처리했다.
Docker로 셀프호스팅하면 이 데이터가 외부 SaaS를 거치지 않는다는 점까지가 Zapier나 Make 대신 n8n을 고르는 이유다.
한계도 분명하다.
분기가 많은 비즈니스 로직은 노드를 이어 붙이기보다 코드로 짜는 편이 낫고, 대량 데이터를 실시간으로 처리하는 용도에는 별도 파이프라인이 맞다.
워크플로우가 JSON으로 저장되다 보니 코드에 비해 Git으로 변경 이력을 관리하기도 번거롭다.
그래서 복잡한 API 서버를 n8n으로 만들 생각은 없다.
다만 이벤트를 받아 다른 시스템으로 전달하고 응답을 가공해 돌려주는 수준의 라우터는 적합한 활용처가 될 수 있다.
모니터링 알림을 조건에 따라 메신저 채널로 나눠 보내거나, 폼으로 들어온 접수 이벤트를 사내 도구로 넘기는 정도의 일에 서버 코드를 새로 추가하는 것은 오히려 과하다.
이런 자리에는 위에서 만든 구성에서 URL과 노드만 바꿔 그대로 쓸 수 있다.