API 문서
개요
BotBell API를 사용하면 프로그램과 스크립트에서 Apple 기기로 푸시 알림을 보내고 답장을 받을 수 있습니다. SDK가 필요 없습니다 — 간단한 HTTP 요청만으로 충분합니다.
Base URL: https://api.botbell.app작동 방식
- BotBell 앱에서 봇을 만들고 푸시 URL을 복사하세요
- 아무 언어나 도구에서 푸시 URL로 JSON 메시지를 POST하세요
- 모든 기기에서 즉시 푸시 알림을 받으세요
- 선택적으로, 답장 URL 콜백 또는 폴링 API를 통해 내 답장을 수신하세요
응답 형식
모든 API 응답은 동일한 JSON 구조를 사용합니다. code가 0이면 성공이며, 그 외의 값은 오류를 나타냅니다.
// Success
{
"code": 0,
"message": "success",
"data": { ... }
}
// Error
{
"code": 40001,
"message": "Invalid bot token"
}메시지 전송
봇에서 BotBell 앱으로 알림을 보냅니다. 봇의 푸시 URL에 이미 인증 토큰이 포함되어 있으므로 POST하기만 하면 됩니다.
엔드포인트
POST https://api.botbell.app/v1/push/<your_bot_token>Authorization 헤더가 필요 없습니다. URL에 포함된 토큰이 인증 수단입니다.
또는 POST /v1/messages/push에 X-Bot-Token: <token> 헤더를 사용할 수도 있습니다.
요청 본문 (JSON)
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
message | string | 예 | 메시지 텍스트 (최대 4,096자) |
title | string | 아니오 | 메시지 위에 표시되는 제목 (최대 256자) |
url | string | 아니오 | 알림에 첨부되는 탭 가능한 링크 |
imageUrl | string | 아니오 | 알림에 표시할 이미지 URL |
요청 예시
curl -X POST https://api.botbell.app/v1/push/bt_your_token \
-H "Content-Type: application/json" \
-d '{
"message": "Server CPU is at 95%!",
"title": "Alert",
"url": "https://grafana.example.com/dashboard"
}'응답
{
"code": 0,
"message": "success",
"data": {
"message_id": "msg_abc123",
"delivered": true,
"timestamp": 1709827200
}
}| 필드 | 설명 |
|---|---|
message_id | 이 메시지의 고유 ID |
delivered | 푸시 알림이 APNs에 성공적으로 전송되면 true, 등록된 기기가 없으면 false |
timestamp | 메시지가 생성된 Unix 타임스탬프 (초) |
지금 사용해 보기
Bot Token을 붙여넣고 기기로 실제 푸시 알림을 보내보세요.
답장 수신
앱에서 봇에 답장하면 BotBell이 자동으로 답장을 서버에 전달할 수 있습니다. 이 기능은 선택 사항이며 — 봇이 양방향 대화를 처리하는 경우에만 필요합니다.
답장 URL 설정
BotBell 앱에서 봇 설정으로 이동하여 답장 URL을 입력하세요 (예: https://your-server.com/botbell/reply). BotBell이 내 답장을 실시간으로 이 URL에 POST합니다.
콜백 페이로드
답장하면 BotBell이 다음 형식으로 답장 URL에 POST 요청을 보냅니다:
POST https://your-server.com/botbell/reply
Headers:
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Webhook-Timestamp: 1709827300
Body:
{
"event": "user_reply",
"bot_id": "bot_abc123",
"message_id": "msg_xyz789",
"content": "Got it, thanks!",
"timestamp": 1709827300
}서명 검증
각 콜백에는 BotBell에서 보낸 것인지 확인할 수 있도록 HMAC-SHA256 서명이 포함됩니다. 서명 키는 봇 설정에 표시된 Webhook Secret입니다.
signature = HMAC-SHA256(
key: <your_webhook_secret>,
message: "<timestamp>.<raw_request_body>"
)서명 검증 방법:
- X-Webhook-Timestamp 및 X-Webhook-Signature 헤더를 추출하세요
- 타임스탬프가 현재 시간으로부터 5분 이내인지 확인하세요 (리플레이 공격 방지)
- Webhook Secret을 키로 사용하여 "<timestamp>.<raw_body>" 문자열에 대해 HMAC-SHA256을 계산하세요
- 계산된 값을 "sha256=" 접두사 뒤의 서명과 비교하세요
서버 응답
수신 확인을 위해 HTTP 2xx 상태 코드를 반환하세요. 2xx가 아닌 응답이나 시간 초과(5초)는 실패로 처리되며 — 답장은 24시간 동안 폴링 큐에 저장되어 나중에 가져올 수 있습니다.
답장 폴링
콜백을 수신할 공개 서버가 없는 경우 폴링으로 답장을 가져올 수 있습니다. 답장 URL이 일시적으로 다운되었을 때 폴백으로도 유용합니다.
엔드포인트
GET https://api.botbell.app/v1/messages/poll
Headers:
X-Bot-Token: <your_bot_token>쿼리 파라미터
| 필드 | 타입 | 기본값 | 설명 |
|---|---|---|---|
since | integer | - | 이 Unix 타임스탬프 이후의 메시지만 반환 |
limit | integer | 20 | 반환할 메시지 수 (최대 100) |
응답
{
"code": 0,
"message": "success",
"data": {
"messages": [
{
"message_id": "msg_xyz789",
"content": "Got it, thanks!",
"timestamp": 1709827300
}
],
"has_more": false
}
}참고: 폴링된 메시지는 읽음으로 표시되며 다시 반환되지 않습니다. 폴링되지 않은 메시지는 24시간 후 만료됩니다.
오류 코드
| 코드 | HTTP 상태 | 설명 |
|---|---|---|
| 40001 | 401 | 유효하지 않거나 누락된 봇 토큰 |
| 40010 | 400 | 요청 유효성 검사 실패 (예: message 필드 누락, 본문이 너무 큼) |
| 40029 | 429 | 속도 제한 초과 — 분당 요청 수 초과 |
| 40030 | 429 | 월간 메시지 할당량 소진 |
| 40031 | 403 | 플랜의 봇 개수 한도 도달 |
| 50000 | 500 | 내부 서버 오류 |
속도 제한 및 할당량
| 제한 | 값 |
|---|---|
| 푸시 속도 (봇당) | 60 |
| 메시지 본문 (글자 수) | 4,096 |
| 제목 (글자 수) | 256 |
| 무료 월간 할당량 | 봇당 300건 메시지 |
| 폴링 메시지 보관 기간 | 24h |
| 답장 URL 시간 초과 | 5s |
속도 제한 상태는 응답 헤더에 반환됩니다:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1709827260코드 예제
메시지 전송
curl -X POST https://api.botbell.app/v1/push/bt_your_token \
-H "Content-Type: application/json" \
-d '{"message":"Server CPU at 95%","title":"Alert"}'import requests
resp = requests.post(
"https://api.botbell.app/v1/push/bt_your_token",
json={
"message": "Build #42 succeeded",
"title": "CI/CD",
"url": "https://ci.example.com/builds/42",
},
)
print(resp.json()) # {"code": 0, "data": {"message_id": "...", ...}}const resp = await fetch("https://api.botbell.app/v1/push/bt_your_token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: "New order received!",
title: "E-Commerce",
}),
});
const data = await resp.json();
console.log(data);package main
import (
"bytes"
"encoding/json"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]string{
"message": "Disk usage above 90%",
"title": "Alert",
})
http.Post(
"https://api.botbell.app/v1/push/bt_your_token",
"application/json",
bytes.NewReader(body),
)
}<?php
$ch = curl_init('https://api.botbell.app/v1/push/bt_your_token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Payment received: $49.99',
'title' => 'Stripe',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);답장 서명 검증
import hmac, hashlib
def verify_signature(payload: bytes, timestamp: str, signature: str, secret: str) -> bool:
message = f"{timestamp}.{payload.decode()}"
expected = hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)const crypto = require("crypto");
function verifySignature(payload, timestamp, signature, secret) {
const message = `${timestamp}.${payload}`;
const expected = crypto.createHmac("sha256", secret).update(message).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(`sha256=${expected}`),
Buffer.from(signature),
);
}