안녕하세요. 스타트업에서 프론트엔드 개발을 하고 있는 inseong입니다. 😁
서비스를 운영하다 보면 고객의 1:1 문의, 오류 제보, 결제 실패와 같은 이벤트를 최대한 빠르게 확인하는 것이 중요합니다.
이번글에서는 디스코드 웹훅으로 고객으로 부터 오는 1:1문의 알림을 받는 방법에 대해서 소개해 보겠습니다.
1:1 문의 알림 흐름도

1️⃣ 사용자가 앱에서 제목, 내용, 사진을 입력해 1:1 문의를 작성합니다.
2️⃣ 사용자가 문의하기 버튼을 누르면 문의 제출 함수가 실행됩니다.
3️⃣ 문의에 사진이 포함되어 있다면 사진을 먼저 S3에 업로드합니다. 사진이 없다면 업로드 과정 없이 다음 단계로 넘어갑니다
4️⃣ 사진 업로드가 완료되면 제목, 내용, 사진 정보 등을 Firestore에 저장합니다. Firestore에서는 이를 새로운 문의 문서를 생성한다고 표현합니다.
5️⃣ Firestore에 문의 문서가 생성되면 Firebase Functions의 함수가 해당 생성 이벤트를 감지해 자동으로 실행됩니다.
6️⃣ 실행된 Firebase 함수는 Secret Manager에 미리 저장해둔 Discord Webhook URL을 가져옵니다.
7️⃣ Firebase 함수는 가져온 Webhook URL로 “새 1:1 문의가 도착했습니다.”라는 메시지를 HTTP POST 방식으로 전송합니다.
8️⃣ Discord Webhook이 전달받은 메시지를 지정된 Discord 채널에 게시하고, Firebase Functions에는 전송 성공 또는 실패 로그가 기록됩니다.
1:1 문의 작성
▫️ 사용자가 앱에서 1:1 문의를 작성합니다.

문의 제출
▫️사용자가 문의 보내기 버튼을 누르면 문의를 제출하는 함수가 실행됩니다.
▫️ 이 함수는 중복 제출을 방지하고 제출 상태와 오류 메시지를 관리한 뒤, 실제 문의를 생성하는 submitSupportInquiry() 서비스를 호출합니다.
const submit = useCallback(
async (input: SubmitSupportInquiryInput) => {
if (!uid || isSubmitting) {
return false;
}
setSubmitErrorMessage(null);
setIsSubmitting(true);
try {
await submitSupportInquiry(uid, input);
return true;
} catch (error) {
setSubmitErrorMessage(getSupportInquiryErrorMessage(error, 'submit'));
return false;
} finally {
setIsSubmitting(false);
}
},
[isSubmitting, uid],
);
사진 업로드 & 문의 저장
▫️문의에 첨부된 사진이 있다면 사진을 하나씩 S3에 업로드합니다.
▫️모든 사진 업로드가 완료되면 업로드된 사진 정보를 포함하여 문의를 Firestore에 저장합니다.
export async function submitSupportInquiry(
uid: string,
input: SubmitSupportInquiryInput,
) {
if (input.photoAttachments.length > 3) {
throw new Error('문의에는 사진을 최대 3장까지 첨부할 수 있습니다.');
}
const photos = [];
for (const attachment of input.photoAttachments) {
photos.push(await uploadSupportInquiryPhoto(attachment));
}
return createSupportInquiry(uid, {
activePocketId: input.activePocketId,
body: input.body,
photos,
title: input.title,
userDisplayName: input.userDisplayName,
});
}
FireStore 문서 생성 및 저장
▫️사진 업로드가 완료되면 제목, 내용, 사진 정보 등을 포함한 문의 데이터를 Firestore에 저장합니다.
▫️Firestore에서는 데이터를 저장하는 단위를 문서라고 하며, 문의가 저장되는 순간 새로운 문의 문서가 생성됩니다.
▫️ 문의 문서는 해당되는 경로에 생성되며, 이후 Firebase Functions가 이 문서 생성 이벤트를 감지하게 됩니다.
Firebase Functions 실행
▫️Firestore에 새로운 문의 문서가 생성되면 Firebase Functions가 해당 문서 생성 이벤트를 자동으로 감지합니다.
▫️onDocumentCreated() 이벤트를 사용하여 문의 문서가 생성되는 순간 서버 함수가 실행되도록 구성했습니다.
▫️앱이 직접 Discord 알림을 호출하는 것이 아니라, Firestore의 문서 생성 이벤트를 계기로 Firebase Functions가 자동으로 실행됩니다.
① 함수 등록 : onDocumentCreated()
② 문서 감지 : document
③ 최대 동시 실행 개수 : maxInstances
④ 리전 설정 : region
⑤ Secret 사용 : secrets
⑥ 이벤트 데이터 확인 : event.data
⑦ Secret 값 읽기 : DISCORD_SUPPORT_WEBHOOK_URL.value();
⑧ Discord에 HTTP 요청 : await fetch
⑨ 전송하는 데이터 : content
export const notifyDiscordOnSupportInquiryCreated = onDocumentCreated(
{
document: 'users/{uid}/supportInquiries/{inquiryId}',
maxInstances: 10,
region: FUNCTION_REGION,
secrets: [DISCORD_SUPPORT_WEBHOOK_URL],
},
async (event) => {
if (!event.data) {
logger.warn('Support inquiry created event had no snapshot.', {
eventId: event.id,
});
return;
}
const webhookUrl = DISCORD_SUPPORT_WEBHOOK_URL.value();
const response = await fetch(webhookUrl, {
body: JSON.stringify({
allowed_mentions: { parse: [] },
content: DISCORD_SUPPORT_MESSAGE,
}),
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
method: 'POST',
});
if (!response.ok) {
const responseText = await response.text();
logger.error('Discord support inquiry notification failed.', {
eventId: event.id,
inquiryId: event.params.inquiryId,
responseText: responseText.slice(0, 300),
status: response.status,
});
throw new Error(
`Discord notification failed with HTTP ${response.status}`,
);
}
logger.info('Discord support inquiry notification sent.', {
eventId: event.id,
inquiryId: event.params.inquiryId,
});
},
);
Discord Webhook URL 불러오기
▫️Discord에서 문의 알림을 받을 채널을 생성한 뒤, 채널 설정 → 연동 → 웹후크(Webhook) 메뉴로 이동합니다.


▫️새 웹후크 만들기를 선택한 후, 웹후크 이름과 알림을 받을 채널을 지정합니다.
▫️이름 : 원하는 웹후크 이름
▫️채널 : 문의 알림을 받을 Discord 채널

Discord Webhook으로 알림 전송
▫️발급받은 Webhook URL은 코드에 직접 작성하지 않고 Firebase Secret Manager에 환경변수로 저장합니다.
▫️Firebase Functions가 실행되면 Secret Manager에서 Webhook URL을 불러옵니다.
▫️불러온 Webhook URL로 HTTP POST 요청을 전송하여 Discord 채널에 "새 1:1 문의가 도착했습니다."라는 알림 메시지를 보냅니다.
터미널로 Firebase Secret 등록
▫️환경변수명 : Firebase Secret에 등록할 환경변수 이름 (ex. DISCORD_SUPPORT_WEBHOOK_URL)
▫️Firebase 프로젝트명 : Secret을 등록할 Firebase 프로젝트 이름
▫️등록이 완료되면 Firebase Functions는 실행 시 Secret Manager에서 해당 Webhook URL을 안전하게 불러와 사용할 수 있습니다.
npx.cmd --yes firebase-tools@15.23.0 functions:secrets:set "환경변수명" --project "Firebase 프로젝트명"
알림 전송 결과 기록
▫️Discord Webhook으로 알림 전송이 완료되면 Firebase Functions에 성공 로그를 기록합니다.
▫️알림 전송에 실패한 경우에는 HTTP 상태 코드와 응답 내용을 함께 기록하여 원인을 빠르게 확인할 수 있도록 구성했습니다.
▫️또한 응답 본문은 최대 300자까지만 저장하여 불필요하게 긴 로그가 쌓이지 않도록 했습니다.
① 앱안에서 고객센터 문의

② PC 또는 모바일 디스코드 앱에서의 알림

③ 관리자 페이지의 문의 확인

이렇게 디스코드 Webhook을 활용하면 고객의 1:1 문의를 실시간으로 받아 빠르게 대응할 수 있습니다.
구현 자체도 비교적 간단하면서 운영 효율을 크게 높여주는 방법이기 때문에
고객 문의뿐만 아니라 서버 오류, 결제 실패, 신규 회원 가입 등 다양한 운영 알림에도 충분히 활용할 수 있습니다.
이번 글이 디스코드 Webhook을 이용한 운영 자동화를 고민하고 계신 분들께 도움이 되었길 바랍니다. 감사합니다. 😊