기능
대기 알림
장소의 실시간 대기 시간이 기준 아래로 줄어들면 Waity가 웹훅을 호출하므로, 에이전트가 계속 물어볼 필요가 없습니다. Free를 포함한 모든 플랜에서 쓸 수 있습니다.
작동 방식
한 장소에 대해 기준 시간(분)과 https 웹훅으로 알림을 만드세요. 매분 데이터 수집이 끝날 때마다 Waity는 방금 새 측정값이 들어온 장소를 확인하고, 대기 시간이 기준 이하이면 서명된 JSON 본문을 웹훅으로 POST합니다.
알림은 GET /v1/places/{id}와 같은 실시간 응답을 읽으므로 API와 어긋나지 않습니다. now.usable 값이 true이고, 단위가 분이며, now.value 값이 under 이하일 때만 발동합니다. 하한값(“60분 초과”), 오래되었거나 일시 중지된 값, 문을 닫은 장소, 데이터 없음은 절대 발동하지 않습니다.
알림 만들기
API 키와 JSON 본문으로 POST /v1/alerts를 호출합니다. 응답은 알림이며, ID와 서명 시크릿이 함께 옵니다. 시크릿은 한 번만 표시되니 웹훅이 읽을 수 있는 곳에 저장하세요.
| 필드 | 받는 값 |
|---|---|
place_id | 하나의 장소, 레인 또는 부서. 공항, 국경 검문소, 병원 캠퍼스에는 단일 대기 시간이 없으므로, 그 ID는 레인이나 부서 ID와 함께 거부됩니다. |
under | 정수 분, 1부터 600까지. |
webhook_url | Waity가 POST하는 곳. https만 가능합니다. 아래 규칙을 참고하세요. |
repeat | false(기본값)는 한 번 발동한 뒤 알림이 fired 상태가 됩니다. true는 대기 시간이 기준보다 5분 넘게 올라간 뒤에 다시 발동하며, 최대 30분에 한 번입니다. |
expires_at | 선택 사항, ISO 8601. 기본값은 지금부터 24시간 후, 최대 30일입니다. |
curl -X POST "https://api.waity.ai/v1/alerts" \
-H "X-Api-Key: $WAITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"place_id":"pl_nvk0wkb3ps","under":30,"webhook_url":"https://example.com/waity/webhook"}'
201응답
{
"id": "al_3k9qx2m7t4p8w1za",
"place": {
"id": "pl_nvk0wkb3ps",
"name": "Ysleta Passenger Vehicles, Standard",
"category": "border"
},
"under": 30,
"unit": "min",
"repeat": false,
"webhook_url": "https://example.com/waity/webhook",
"status": "armed",
"created_at": "2026-10-31T19:02:11Z",
"expires_at": "2026-11-01T19:02:11Z",
웹훅 URL 규칙
알림이 사설 네트워크에 닿지 않도록, Waity는 다음과 같은 웹훅 URL을 거부합니다:
- 기본 포트(443)에서 https를 쓰지 않거나, 사용자 이름이나 비밀번호가 들어 있는 URL;
- 호스트 이름 대신 IP 주소를 쓰는 URL. 따라서 사설, 루프백, 링크 로컬, 클라우드 메타데이터 주소는 모두 거부됩니다;
- localhost, 단일 레이블 이름, 또는 .local, .internal, .lan, .home.arpa 같은 사설·예약 이름을 쓰는 URL;
- Waity 자체를 가리키는 URL.
Waity는 이름 조회를 직접 하지 않으며 리디렉션을 따르지 않습니다: 3xx 응답은 전송 실패로 처리됩니다.
목록, 조회, 삭제
GET /v1/alerts는 알림을 최신순으로, 상태와 마지막 전송 정보와 함께 반환합니다. 한 계정의 키들은 하나의 목록을 공유하고, 계정 없는 키는 자체 목록을 가집니다. GET /v1/alerts/{id}는 알림 하나를 조회하고, DELETE /v1/alerts/{id}는 알림을 삭제하고 전송을 멈춥니다.
curl
curl "https://api.waity.ai/v1/alerts" \
-H "X-Api-Key: $WAITY_API_KEY"
curl -X DELETE "https://api.waity.ai/v1/alerts/al_3k9qx2m7t4p8w1za" \
-H "X-Api-Key: $WAITY_API_KEY"
| 상태 | 받는 값 |
|---|---|
armed | 대기 시간이 기준 아래로 줄어들기를 기다리는 중입니다. |
fired | 발동한 일회성 알림입니다. |
expired | 발동하지 않은 채 expires_at이 지났습니다. |
failing | 마지막 전송 시도가 실패했습니다. last_delivery를 참고하세요. 남은 재시도가 있으면 계속됩니다. |
disabled | 전송 시도가 20회 연속 실패했습니다. 웹훅이 작동하면 새 알림을 만드세요. |
웹훅
각 전송은 JSON 본문이 있는 POST입니다. 사용자에게 먼저 warnings를 읽어 준 다음 summary를 전하세요. now.value 값은 now.usable_until까지 유효하며, 그 후에는 GET /v1/places/{id}를 다시 요청하세요.
POST
{
"type": "wait.under",
"alert": {
"id": "al_3k9qx2m7t4p8w1za",
"under": 30
},
"place": {
"id": "pl_nvk0wkb3ps",
"name": "Ysleta Passenger Vehicles, Standard",
"category": "border",
"url": "https://waity.ai/places/ysleta-passenger-vehicles-standard-kb3ps"
},
"now": {
"value": 25,
"unit": "min",
"measured_at": "2026-10-31T13:58:00-06:00",
"usable_until": "2026-10-31T15:28:00-06:00",
"basis": "estimate"
},
"summary": "About 25 min to enter the US, estimated by the border agency, updated 2 min ago.",
"warnings": [],
"links": {
"place": "https://api.waity.ai/v1/places/pl_nvk0wkb3ps",
"page": "https://waity.ai/places/ysleta-passenger-vehicles-standard-kb3ps",
"alert": "https://api.waity.ai/v1/alerts/al_3k9qx2m7t4p8w1za",
"docs": "https://waity.ai/docs/alerts"
}
}
각 요청에는 다음 헤더가 있습니다:
Waity-Signature: t=<unix>,v1=<hex>:t는 서명한 시각(unix 초)이고,v1은t, 점, 원본 본문을 이은 문자열의 HMAC-SHA256이며, 알림의 시크릿 전체를 키로 씁니다.Waity-Alert: 알림의 ID.Waity-Delivery: 전송의 ID. 재시도할 때마다 같으므로 중복을 버릴 수 있습니다.
서명 검증
JSON을 파싱하기 전에 원본 바이트로 서명을 확인하고, 상수 시간으로 비교하며, 5분보다 오래된 t는 거부하세요.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.WAITY_ALERT_SECRET; // whsec_…
// The raw body: the signature covers the exact bytes Waity sent.
app.post("/waity/webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = Object.fromEntries(
(req.get("Waity-Signature") ?? "").split(",").map((p) => p.split("=", 2)),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
const ok =
fresh &&
typeof parts.v1 === "string" &&
parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body);
// Read event.warnings first, then event.summary.
res.status(204).end();
});
app.listen(3000);
재시도
10초 안에 아무 2xx로나 응답하세요. 그 밖의 응답은 실패한 시도이며, 약 1, 5, 25분 뒤에 재시도됩니다. 20회 연속 실패하면 알림이 비활성화됩니다.
플랜별 한도
플랜마다 동시에 활성화할 수 있는 알림 수가 정해져 있습니다. 하나를 더 만들면 플랜 한도를 알려 주는 메시지와 함께 409 alert_limit가 응답됩니다. 알림 요청은 각각 호출 1회로 계산되며, 전송은 계산되지 않습니다. 계정 없는 키는 Free로 계산됩니다.
| 플랜 | 활성 알림 |
|---|---|
| Free | 1 |
| Plus | 10 |
| Pro | 100 |
| Team | 1,000 |
응급실과 긴급 진료소
응급실과 긴급 진료소 알림에서는 모든 응급실 응답과 마찬가지로, 각 전송의 warnings[0]가 911 안내이며 한 번만 말합니다.
관련 문서
다음는 기록을 바탕으로 대기 시간이 언제 줄어들지 알려 줍니다. 응답 읽기에서 모든 필드를 설명하며, 모든 필드는 API 레퍼런스에 있습니다.