웹훅(webhook)이란?
작성자 Uttam Singh

Webhook은 HTTP POST로 전달되는 이벤트 알림입니다. 한 시스템에서 어떤 일이 발생하면 그 시스템이 직접 관리하는 URL로 요청을 보내고, 요청 본문에는 발생한 일이 담겨 있습니다. 앱이 변경 사항이 있는지 따로 물어볼 필요가 없습니다.
블록체인 앱에서는 이벤트가 온체인에서 발생합니다. 사용자의 지갑이 USDC를 받거나, NFT의 소유자가 바뀌거나, 직접 배포한 컨트랙트가 로그를 발생시킵니다. Webhook이 없으면 백엔드는 타이머에 맞춰 노드나 API를 폴링하고 결과를 비교해 이러한 변경을 잡아내야 합니다. Webhook을 사용하면 변경이 감지되는 즉시 서버로 전송됩니다.
이 가이드에서는 webhook URL, 엔드포인트, 페이로드가 무엇인지, webhook이 API와 어떻게 다른지, 전달과 재시도가 어떻게 작동하는지, 요청을 검증하는 방법, 그리고 프로덕션에서 중복과 리오그를 처리하는 방법을 다룹니다.
Webhook이란 무엇인가?
Webhook은 두 당사자를 연결합니다. 발신자는 이벤트를 감시합니다. 수신자는 URL을 공개하는 여러분의 앱입니다. 구독한 조건과 일치하는 이벤트가 발생하면 발신자는 이벤트를 설명하는 JSON 본문을 담아 해당 URL로 HTTP POST 요청을 보내고, 서버는 200 상태 코드로 응답해 요청이 도착했음을 확인합니다. 서버 쪽에서 돌려보내는 데이터는 없으므로 webhook은 단방향 서버 간 메시지입니다.
대안은 폴링으로, 정해진 주기마다 소스에 업데이트를 요청하는 방식입니다. 이 요청 대부분은 새로운 내용 없이 돌아오고, 이벤트는 앱이 확인하기 전까지 최대 한 번의 폴링 간격 동안 감지되지 않은 채 남아 있을 수 있습니다. Webhook은 보고할 이벤트가 있을 때만 요청을 보내므로 두 문제를 모두 피할 수 있습니다.
폴링은 주문한 물건이 도착했는지 매시간 매장에 전화해 묻는 것과 같습니다. Webhook은 물건이 도착하면 매장이 문자로 알려주는 것입니다.
Webhook URL이란 무엇인가?
Webhook URL은 발신자가 이벤트를 전달하는 공개 주소로, https://api.example.com/webhooks/alchemy와 같은 형태입니다. Webhook을 생성할 때 이 URL을 제공업체에 등록합니다. 요청은 제공업체의 서버가 보내므로 URL은 인터넷에서 접근할 수 있어야 하며, 페이로드에 사용자의 활동 정보가 담기므로 HTTPS를 사용해야 합니다. 개발 중에는 ngrok 같은 터널링 도구를 사용해 노트북에서 실행 중인 서버에 임시 공개 URL을 부여할 수 있습니다. Alchemy의 webhook 빠른 시작 가이드에서 이 설정 과정을 안내합니다.
Webhook 엔드포인트란 무엇인가?
Webhook 엔드포인트는 webhook URL로 전송된 요청을 처리하는 서버의 경로입니다. 두 용어는 흔히 같은 의미로 쓰입니다. URL은 주소이고, 엔드포인트는 그 뒤에서 동작하는 코드입니다.
엔드포인트가 하는 일은 최소한으로 유지하세요. 엔드포인트는 요청이 제공업체에서 왔는지 확인하고, 이벤트를 저장한 뒤 200을 반환합니다. 잔액 업데이트나 푸시 알림 전송처럼 시간이 더 걸리는 작업은 백그라운드 워커에서 처리해야 합니다. 이런 작업을 요청 안에서 실행하면 느린 데이터베이스 호출 때문에 제공업체가 전달에 실패했다고 판단할 수 있습니다.
Webhook 페이로드란 무엇인가?
페이로드는 요청의 JSON 본문입니다. Alchemy의 모든 webhook은 동일한 최상위 구조를 사용하며, event 필드에 해당 webhook 유형의 데이터가 담깁니다. 다음은 지갑 주소 목록을 감시하는 유형인 Address Activity webhook이 Ethereum의 USDC 전송에 대해 보낸 페이로드입니다. 길이를 줄이기 위해 일부 필드는 생략했습니다.
{
"webhookId": "wh_k63lg72rxda78gce",
"id": "whevt_vq499kv7elmlbp2v",
"createdAt": "2024-01-23T07:42:26.411977228Z",
"type": "ADDRESS_ACTIVITY",
"event": {
"network": "ETH_MAINNET",
"activity": [
{
"blockNum": "0xdf34a3",
"hash": "0x7a4a39da2a3fa1fc2ef88fd1eaea070286ed2aba21e0419dcfb6d5c5d9f02a72",
"fromAddress": "0x503828976d22510aad0201ac7ec88293211d23da",
"toAddress": "0xbe3f4b43db5eb49d1f48f53443b9abce45da3b79",
"value": 293.092129,
"asset": "USDC",
"category": "token",
"rawContract": {
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"decimals": 6
},
"log": {
"logIndex": "0x6e",
"removed": false
}
}
]
}
}webhookId는 어떤 webhook이 트리거되었는지, id는 이 이벤트를, type은 webhook 유형을 나타냅니다. activity의 각 항목은 감시 중인 주소가 관련된 전송 한 건입니다. 토큰을 식별하려면 토큰의 컨트랙트 주소인 rawContract.address를 사용하세요. asset 심볼은 토큰 컨트랙트 자체에서 가져오는 값이며, 누구나 자신을 USDC라고 칭하는 토큰을 배포할 수 있습니다.
Webhook은 API와 어떻게 다른가?
API는 호출되기를 기다립니다. 앱이 요청을 보내면 데이터를 돌려받습니다. Webhook은 방향이 반대입니다. 제공업체가 보고할 내용이 있을 때 앱을 호출합니다. 이렇게 방향이 뒤바뀌기 때문에 webhook을 역방향 API라고 부르기도 합니다.
대부분의 프로덕션 앱은 둘 다 사용합니다. Webhook은 무언가 발생했다는 사실을 알려주고, API 호출로 나머지 정보를 채웁니다. 예를 들어 결제 백엔드는 입금 webhook을 받은 뒤 Token API를 호출해 지갑의 현재 잔액을 조회한 다음 사용자에게 보여줄 수 있습니다.
Webhook은 이벤트를 받는 쪽이 서버 엔드포인트일 때 적합합니다. 트레이딩 봇이나 실시간 대시보드처럼 받는 쪽이 장시간 실행되는 프로세스라면, WebSocket 구독이 연결 하나를 열어 둔 채 그 연결로 이벤트를 스트리밍합니다. 어떤 방식을 선택해야 하는지는 webhook, WebSocket, gRPC 비교 글에서 다룹니다.
Webhook은 어떻게 작동하는가?
Webhook 전달은 매번 같은 단계를 거칩니다.
- 구독합니다. 대상 URL과 필터를 지정해 webhook을 생성합니다. 필터는 감시할 네트워크와 그 네트워크에서 감시할 대상(주소 목록이나 컨트랙트의 이벤트 등)을 정합니다.
- 이벤트가 일치합니다. 새 블록에 필터와 일치하는 활동이 포함됩니다.
- 제공업체가 POST를 보냅니다. 요청에는 페이로드와 서버가 확인할 수 있는 서명 헤더가 담깁니다.
- 서버가 수신을 확인합니다. 200 응답을 보내면 이벤트가 전달 완료로 처리됩니다.
- 실패한 전달은 재시도됩니다. 서버가 다운되었거나, 시간 초과되었거나, 그 외의 상태 코드를 반환하면 제공업체가 나중에 다시 시도합니다.
Alchemy는 재시도에 지수 백오프를 사용합니다. 즉, 실패할 때마다 다음 시도까지의 대기 시간이 늘어납니다. webhook 빠른 시작 가이드의 전달 동작 항목에 설명된 대로, Free 및 Pay As You Go 플랜에서는 최대 10분, Enterprise 플랜에서는 최대 1시간 동안 재시도합니다.
처음 전송되는 알림은 블록 생성 속도와 관계없이 모든 체인에서 순서대로 전달됩니다. 이 보장은 첫 번째 시도에만 적용되므로, 재시도된 이벤트는 더 새로운 이벤트보다 늦게 도착할 수 있습니다. 각 이벤트와 함께 블록 번호를 저장하고, 도착 시간이 아닌 블록 번호를 기준으로 레코드를 정렬하세요.
서버가 재시도 기간이 끝난 뒤에도 다운되어 있으면 그 기간의 이벤트는 다시 전달되지 않으므로, 체인의 이력에서 누락된 부분을 채우세요. Ethereum과 Transfers API가 지원하는 L2에서는 놓친 주소와 블록 범위에 대해 Transfers API를 조회하세요. Stellar에서는 Stellar 전송 엔드포인트가 계정의 전송을 원장(ledger) 범위로 필터링해 반환합니다. Solana에서는 getSignaturesForAddress로 감시 중인 각 주소의 트랜잭션을 거슬러 올라가며 페이지 단위로 조회하세요.
블록체인 앱에서 webhook은 어디에 사용되는가?
Alchemy는 네 가지 webhook 유형을 제공하며, 어떤 유형이 적합한지는 무엇을 감시해야 하는지에 따라 달라집니다.
Address Activity webhook은 가장 흔한 작업인 지갑 집합 감시를 담당합니다. 결제 백엔드는 입금 주소를 감시하다가 USDC가 도착하면 해당 사용자에게 입금을 반영합니다. 지갑 앱은 사용자가 토큰을 받으면 푸시 알림을 보냅니다. ERC-20 전송에는 토큰의 컨트랙트 주소가 포함되며, 결제 백엔드는 이 주소로 입금이 실제 USDC인지 확인합니다.
NFT Activity webhook은 NFT 컨트랙트에 대해 같은 역할을 하며, 마켓플레이스와 컬렉션 대시보드에 적합합니다. 그 밖의 모든 경우는 Custom webhook이 처리합니다. 특정 이벤트 시그니처를 가진 자체 컨트랙트의 로그처럼 원하는 블록 데이터를 선택하는 GraphQL 쿼리를 작성하면, 새 블록마다 해당 데이터만 담아 webhook이 트리거됩니다. Stellar Address Activity webhook은 Soroban 컨트랙트 이벤트를 포함해 Stellar에서도 주소 추적을 제공합니다.
각 webhook은 하나의 네트워크를 감시합니다. 여러 체인을 지원하려면 네트워크마다 webhook을 하나씩 생성하고 모두 같은 URL을 가리키게 하세요. Webhook은 온체인 AI 에이전트에도 적합합니다. 에이전트는 루프를 돌며 폴링하는 대신 감시 중인 지갑에서 자금이 이동할 때 깨어날 수 있습니다.
Webhook 서명은 어떻게 검증하는가?
Webhook URL을 알게 된 사람은 누구나 그 URL로 요청을 보낼 수 있으므로, 엔드포인트는 각 요청을 처리하기 전에 요청이 제공업체에서 왔는지 확인해야 합니다. Alchemy는 모든 webhook 요청에 HMAC-SHA256으로 서명합니다. HMAC-SHA256은 비밀 키를 가진 사람만 재현할 수 있는 키 기반 해시입니다. 키는 webhook의 서명 키이고, 입력값은 요청 본문이며, 16진수 결과는 X-Alchemy-Signature 헤더에 담깁니다. 서버는 수신한 본문에 대해 같은 해시를 계산하고 두 값을 비교합니다.
각 webhook에는 고유한 서명 키가 있으며, Alchemy 대시보드의 해당 webhook 페이지에서 확인할 수 있습니다. 다음 세 가지가 검증이 실제로 보호 효과를 내는지를 결정합니다.
- 원시 바이트를 해시하세요. 서명은 전송된 그대로의 본문을 대상으로 합니다. 프레임워크가 JSON을 파싱하고 코드에서 이를 다시 직렬화하면 공백과 키 순서가 바뀔 수 있어 해시가 일치하지 않습니다.
- 상수 시간으로 비교하세요.
===나==대신 Node에서는crypto.timingSafeEqual, Python에서는hmac.compare_digest를 사용해, 응답 시간으로 위조 서명이 어디까지 맞았는지 드러나지 않게 하세요. - 다른 작업보다 먼저 거부하세요. 401을 반환하고 처리를 중단하세요. 검증하지 않은 페이로드는 파싱하거나 저장하지 마세요.
다음은 세 가지를 모두 따르는 최소한의 Express 엔드포인트입니다.
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
// enqueue is your own durable queue (a database table, SQS, BullMQ).
// It should resolve only after the event is safely stored.
import { enqueue } from "./queue";
const signingKey = process.env.ALCHEMY_WEBHOOK_SIGNING_KEY ?? "";
if (!signingKey) throw new Error("Set ALCHEMY_WEBHOOK_SIGNING_KEY");
function isValidSignature(rawBody: Buffer, signature: string | undefined): boolean {
if (!signature) return false;
const expected = createHmac("sha256", signingKey).update(rawBody).digest();
const received = Buffer.from(signature, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}
const app = express();
// express.raw() keeps the exact bytes Alchemy signed, so don't parse JSON on this route first.
app.post("/webhooks/alchemy", express.raw({ type: "*/*" }), async (req, res) => {
if (!isValidSignature(req.body, req.get("x-alchemy-signature"))) {
res.sendStatus(401);
return;
}
await enqueue(JSON.parse(req.body.toString("utf8")));
res.sendStatus(200);
});
app.listen(8080);두 번째 보호 계층으로, 방화벽에서 Alchemy의 webhook IP 주소만 허용할 수 있습니다. 해당 주소 목록은 webhook 빠른 시작 가이드의 보안 섹션에 나와 있습니다.
중복, 지연, 리오그된 webhook은 어떻게 처리해야 하는가?
Webhook 전달은 최소 한 번 전달(at-least-once)로 간주하세요. 시간 초과 후의 재시도로 서버가 이미 저장한 이벤트가 다시 전달될 수 있으므로, 핸들러는 멱등성을 갖춰야 합니다. 같은 이벤트를 두 번 처리해도 한 번 처리한 것과 결과가 같아야 합니다.
- 요청이 아닌 전송 단위로 중복을 제거하세요. 하나의 페이로드에 여러 전송이 담길 수 있고, 하나의 트랜잭션에도 여러 전송이 포함될 수 있습니다. 네트워크, 트랜잭션
hash, 트랜잭션 내 전송의 위치로 키를 만드세요. 위치는 토큰 전송의 경우log.logIndex, 내부 전송의 경우typeTraceAddress입니다. 외부 전송은 트랜잭션당 하나뿐이므로 해시만 있으면 됩니다. 이 키를 고유 제약 조건이 있는 테이블에 저장하고, 키와log.removed값이 모두 저장된 레코드와 일치하는 전달만 건너뛰세요. - 먼저 저장한 다음 수신을 확인하세요. 이벤트를 영속적으로 저장한 뒤 200을 반환하고, 실제 작업은 워커에서 수행하세요. 응답하기 전에 작업을 수행하는 핸들러는 시간 초과 위험이 있고, 시간 초과는 이미 처리했을 수도 있는 이벤트의 재시도를 유발합니다.
- 리오그된 전송은 되돌리세요. 체인 재구성, 즉 리오그는 최근 블록을 대체하며, 대체된 블록의 트랜잭션은 체인에서 빠집니다. 토큰 전송에는
log.removed필드가 있으며, 값이true이면 해당 트랜잭션이 더 이상 표준 체인에 없다는 뜻입니다.log.removed가true로 설정된 전달은 원래 전송과 같은 키를 가지므로, 저장된 레코드에 대한 업데이트로 처리하세요. 레코드를 제거됨으로 표시하고, 앱이 그 전송에 대해 수행한 작업을 모두 되돌리세요. - 되돌릴 수 없는 작업은 최종성을 기다린 뒤 수행하세요. 대기 중인 입금을 바로 표시하는 것은 합리적입니다. 하지만 자금 지급, 상품 배송, 출금 가능 잔액 반영은 블록이 최종 확정될 때까지 기다려야 합니다. Ethereum에서는
finalized블록 태그로 어떤 블록이 최종 확정되었는지 알 수 있습니다.
Alchemy에서 webhook은 어떻게 설정하는가?
리스너부터 시작하세요. Alchemy의 webhook 빠른 시작 가이드에는 서명 검증이 내장된 JavaScript, Python, Go, Rust 스타터 서버가 있고, webhook 예제 저장소에는 로컬에서 실행할 수 있는 완전한 프로젝트가 있습니다.
그다음 webhook을 생성합니다. 가장 빠른 방법은 webhook 대시보드입니다. 유형과 네트워크를 선택하고, 주소나 필터를 추가하고, URL을 붙여넣은 뒤 Test Webhook 버튼으로 엔드포인트에 샘플 이벤트를 보내세요.
코드로 webhook을 생성하려면 webhook 대시보드 상단에 표시된 인증 토큰으로 Notify API를 호출하세요.
curl -X POST https://dashboard.alchemy.com/api/create-webhook \
-H "X-Alchemy-Token: $ALCHEMY_WEBHOOK_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"network": "BASE_MAINNET",
"webhook_type": "ADDRESS_ACTIVITY",
"webhook_url": "https://api.example.com/webhooks/alchemy",
"addresses": ["0xYOUR_DEPOSIT_ADDRESS"]
}'이후 같은 API로 주소를 추가하거나 제거할 수 있으므로, 신규 사용자마다 입금 주소를 생성하는 앱도 webhook을 수천 개 만들 필요 없이 webhook 하나로 운영할 수 있습니다. Alchemy CLI는 alchemy webhook create, list, update, delete 명령으로 같은 API를 감싸며, 코딩 에이전트가 webhook을 관리할 때 유용합니다.
Alchemy 대시보드 실제로 보기
30분 가이드 투어: 대시보드의 모든 도구를 라이브로 시연합니다.
Watch the demoAlchemy에서 webhook 사용 시작하기
Alchemy Webhooks는 계약이나 최소 약정 없이 Free 플랜에서 사용할 수 있습니다. 첫 webhook으로는 직접 관리하는 지갑에 대한 Address Activity webhook이 좋습니다. 그 지갑으로 소액을 전송하고 페이로드가 엔드포인트에 도착하는지 확인해 보세요. 그다음 Custom webhook을 사용하면 동일한 전달, 재시도, 서명 방식으로 자체 컨트랙트의 이벤트를 감시할 수 있습니다.
자주 묻는 질문
Webhook을 간단히 설명하면 무엇인가?
Webhook은 어떤 일이 발생했을 때 한 앱이 다른 앱으로 자동으로 보내는 메시지입니다. 보내는 앱은 여러분이 제공한 URL로 HTTP POST 요청을 보내며, 요청 본문에 이벤트의 세부 정보를 담습니다. 업데이트가 앱으로 전송되므로 앱이 계속 업데이트를 요청할 필요가 없습니다.
Webhook과 API의 차이는 무엇인가?
API를 사용하면 앱이 요청을 보내고 서버가 데이터로 응답합니다. Webhook을 사용하면 이벤트가 발생했을 때 서버가 요청받지 않아도 앱으로 요청을 보냅니다. API는 필요할 때 데이터를 읽는 데 적합하고, webhook은 이벤트에 대응하는 데 적합합니다. 대부분의 앱은 webhook을 받은 다음 API를 호출해 세부 정보를 가져오는 식으로 둘 다 사용합니다.
지갑에 USDC가 입금되면 webhook 알림을 받으려면 어떻게 해야 하는가?
USDC를 받는 체인에 Alchemy Address Activity webhook을 생성하고, 지갑 주소를 추가한 다음 서버의 URL을 지정하세요. 들어오는 각 전송에는 발신자, 금액, 토큰 컨트랙트 주소가 포함됩니다. 누구나 같은 이름의 토큰을 배포할 수 있으므로 "USDC" 심볼이 아닌 USDC의 컨트랙트 주소로 일치 여부를 확인하세요.
Solana 지갑 활동에 대한 webhook 알림을 받으려면 어떻게 해야 하는가?
Alchemy의 Address Activity webhook은 30개 이상의 EVM 체인과 함께 Solana도 지원하므로, Solana 지갑 주소 집합을 감시하다가 활동이 있을 때 POST 요청을 받을 수 있습니다. Address Activity webhook 문서에서 Solana 전용 설정 가이드로 연결됩니다. 각 요청을 처리하기 전에 서명을 검증하세요.
스마트 컨트랙트에서 이벤트가 발생하면 webhook으로 알림을 받을 수 있는가?
네. Alchemy Custom webhook은 GraphQL 쿼리를 사용하므로, 특정 이벤트 시그니처를 가진 컨트랙트 주소의 로그를 필터링할 수 있습니다. 그러면 새 블록마다 일치하는 로그만 담아 webhook이 트리거됩니다. 각 Custom webhook은 하나의 네트워크를 감시하므로, 컨트랙트가 배포된 체인마다 하나씩 생성하세요.
Webhook이 트리거될 때 서버가 다운되어 있으면 어떻게 되는가?
Alchemy는 실패한 전달을 지수 백오프로 재시도하며, Free 및 Pay As You Go 플랜에서는 최대 10분, Enterprise 플랜에서는 최대 1시간 동안 재시도합니다. 서버가 200을 반환하지 않으면 전달이 실패한 것으로 처리됩니다. 서버가 그보다 오래 다운되어 있었다면 Ethereum과 지원되는 L2에서는 Transfers API를, Solana와 Stellar에서는 각 체인의 자체 이력 엔드포인트를 사용해 체인 이력에서 누락된 활동을 채우세요.
Webhook 하나로 몇 개의 주소를 추적할 수 있는가?
Alchemy Address Activity webhook은 최대 100,000개의 주소를 추적하며, Stellar Address Activity webhook도 마찬가지입니다. Webhook을 다시 생성하지 않고도 Notify API로 주소를 추가하거나 제거할 수 있으므로, 사용자마다 새 입금 주소를 생성하는 앱에 적합합니다.
관련 개요

블록체인 매직을 만드세요
Alchemy는 가장 강력한 Web3 개발자 제품 및 도구를 리소스, 커뮤니티, 그리고 전설적인 지원과 결합합니다.


