개발자
웹훅 및 Zapier
웹훅은 앱이 일시 중지되거나 스마트 링크가 클릭되는 등 워크스페이스에서 어떤 일이 일어나는 즉시 서버로 HTTP 요청을 보내요. 웹훅으로 자체 시스템을 업데이트하거나, Slack에 메시지를 게시하거나, 서버를 작성하지 않고도 mapps.one을 Zapier, Make 또는 n8n에 연결할 수 있어요. 웹훅은 Studio 플랜에서 사용할 수 있으며, 워크스페이스당 최대 10개의 엔드포인트를 만들 수 있어요.
엔드포인트 추가하기
- 대시보드에서 개발자 → 웹훅을 열고 엔드포인트 추가를 선택하세요. 워크스페이스 소유자와 관리자가 이 작업을 할 수 있어요.
- 공개
httpsURL을 입력하고, 보낼 이벤트(또는 모든 이벤트)를 선택하고, 설명을 추가하세요. - 서명 시크릿을 복사해 서버의 다른 시크릿과 함께 보관하세요.
- 테스트 이벤트 보내기를 선택해 엔드포인트가 응답하는지 확인하세요.
엔드포인트 페이지에는 최근 전송 내역이 응답 상태, 응답 본문의 처음 2,000자, 각 시도에 걸린 시간과 함께 표시돼요. API로 엔드포인트를 만들고 삭제할 수도 있어요.
이벤트
| 유형 | 전송 시점 |
|---|---|
app.created | 워크스페이스에 앱이 추가됐어요. |
app.updated | 앱 정보, 링크 또는 페이지 설정이 변경됐어요. |
app.deleted | 앱이 삭제됐어요. |
app.status_changed | 앱이 활성화, 일시 중지 또는 정지됐어요. |
app.metadata_fetched | 앱의 스토어 메타데이터를 가져왔어요. |
legal.published | 개인정보 처리방침 또는 이용약관 문서가 게시됐어요. |
domain.verified | 커스텀 도메인이 인증됐어요. |
link.clicked | 모든 스마트 링크 클릭. 인기 있는 앱이라면 요청이 매우 많을 수 있어요. |
analytics.daily_summary | 앱별 전날 클릭 합계를 하루에 한 번 보내요. |
요청 형식
모든 이벤트는 JSON 본문을 담은 POST 요청으로 전송돼요.
| 헤더 | 값 |
|---|---|
Content-Type | application/json |
User-Agent | mapps.one-Webhooks/1.0 |
X-Mapps-Event | 이벤트 유형(예: link.clicked) |
X-Mapps-Delivery | 이벤트 ID. 재시도할 때마다 같은 값이 유지돼요. |
X-Mapps-Timestamp | 이번 전송 시도의 Unix 시간 |
X-Mapps-Signature | t={timestamp},v1={signature} |
본문은 모든 이벤트에서 형태가 같은 봉투(envelope) 구조예요.
{
"id": "evt_01j8c3m9t5v2x7z4b6d8f0h2k4",
"type": "app.status_changed",
"created_at": "2026-09-14T09:41:07+00:00",
"workspace": {
"id": "01j8a0f4c7d2e9h6k3m5n8q1r4",
"name": "Habitly Labs"
},
"data": {
"app": {
"id": "01j8b2k6r3xq9v7m4n5p0s1t2w",
"slug": "habitly",
"name": "Habitly"
},
"status": "paused"
}
}
data 필드의 내용은 이벤트에 따라 달라요. 앱 관련 이벤트에는 id, slug, name 값을 담은 app 객체가 포함돼요.
link.clicked
"data": {
"app": { "id": "01j8b2k6r3xq9v7m4n5p0s1t2w", "slug": "habitly", "name": "Habitly" },
"click": {
"occurred_at": "2026-09-14 09:41:07",
"os": "iOS",
"device_type": "mobile",
"country": "TR",
"source": "instagram",
"destination": "app_store",
"utm_source": "instagram",
"utm_medium": "social",
"utm_campaign": "launch"
}
}
occurred_at 값은 UTC 기준이에요. device_type 값은 mobile, tablet, desktop 중 하나예요. destination 값은 스토어 키, web, landing 중 하나예요. 알 수 없는 필드는 null 값이에요. 봇 방문은 이 이벤트를 발생시키지 않아요.
app.metadata_fetched
"data": {
"app": { "id": "01j8b2k6r3xq9v7m4n5p0s1t2w", "slug": "habitly", "name": "Habitly" },
"source": "app_store"
}
서명 검증하기
요청이 mapps.one에서 왔고 전송 중에 변경되지 않았는지 확인할 수 있도록 모든 요청의 서명을 검증하세요.
X-Mapps-Signature헤더에서t값과v1값을 읽으세요.- 엔드포인트의 서명 시크릿으로
{t}.{raw request body}문자열의 HMAC-SHA256 값을 소문자 16진수로 계산하세요. - 상수 시간 비교를 사용해 계산한 값을
v1값과 비교하세요. - 재전송된 요청을 막기 위해
t값이 서버 시계와 5분 넘게 차이 나면 요청을 거부하세요.
PHP
<?php
$secret = getenv('MAPPS_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_MAPPS_SIGNATURE'] ?? ''), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (! hash_equals($expected, (string) ($parts['v1'] ?? '')) || abs(time() - $timestamp) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'link.clicked') {
// Store $event['data']['click'] ...
}
http_response_code(200);
Laravel에서는 $request->getContent() 메서드로 본문을, $request->header('X-Mapps-Signature') 메서드로 헤더를 읽고, 해당 라우트를 CSRF 보호에서 제외하세요.
Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const secret = process.env.MAPPS_WEBHOOK_SECRET;
app.post('/webhooks/mapps', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('X-Mapps-Signature') ?? '';
const parts = Object.fromEntries(header.split(',').map((pair) => pair.split('=')));
const timestamp = Number(parts.t);
const body = req.body.toString('utf8');
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
const valid = received.length === expected.length && crypto.timingSafeEqual(received, expected);
if (!valid || Math.abs(Date.now() / 1000 - timestamp) > 300) {
return res.sendStatus(400);
}
const event = JSON.parse(body);
if (event.type === 'link.clicked') {
// Store event.data.click ...
}
res.sendStatus(200);
});
app.listen(3000);
응답 및 재시도
- 10초 안에 2xx 상태로 응답하세요. 오래 걸리는 작업은 응답한 후 백그라운드 작업으로 처리하세요.
- 리디렉션은 따라가지 않으므로 최종 URL을 사용하세요.
- URL은 공개
https주소여야 해요. 사설 네트워크 및 로컬 네트워크 주소는 거부돼요. - 엔드포인트가 시간 초과되거나 2xx 이외의 응답을 반환하면 최대 5번 더 재시도해요. 재시도는 약 1분, 5분, 30분, 2시간, 6시간 후에 이루어져요. 시도할 때마다 새로운 타임스탬프와 서명이 사용돼요.
- 같은 이벤트가 두 번 이상 도착할 수 있고, 이벤트가 순서와 다르게 도착할 수도 있어요. 이벤트
id값인X-Mapps-Delivery헤더로 중복을 무시하고,created_at값으로 순서를 정하세요. - 연속으로 20번 실패하면 엔드포인트가 꺼지고 더 이상 아무것도 전송되지 않아요. 문제를 해결한 다음 대시보드에서 다시 켜세요.
Zapier, Make, n8n
서버를 운영하지 않고도 mapps.one을 수천 개의 앱과 연결할 수 있어요.
Zapier
- Webhooks by Zapier를 트리거로 하는 Zap을 만들고 Catch Hook을 선택하세요.
- Zapier에서 제공하는 웹훅 URL을 복사하세요.
- mapps.one에서 이 URL로 엔드포인트를 추가하고 원하는 이벤트(예:
app.status_changed또는analytics.daily_summary)를 선택하세요. - mapps.one에서 테스트 이벤트 보내기를 선택한 다음 Zapier에서 Test trigger를 선택하세요. 이제
data.app.name같은 페이로드 필드를 다음 단계에서 사용할 수 있어요.
활용 예: 앱이 일시 중지되면 Slack 메시지 게시하기, 매일의 클릭 수를 Google Sheet에 추가하기, 클라이언트의 도메인이 인증되면 클라이언트에게 이메일 보내기.
Make 및 n8n
Make에서는 Webhooks → Custom webhook 트리거를 사용하세요. n8n에서는 POST 메서드로 설정한 Webhook 노드를 사용하세요. 그런 다음 위와 같이 mapps.one에서 해당 URL을 엔드포인트로 추가하세요.
연동 서비스 만들기
다른 사람들이 사용할 mapps.one 연동 서비스를 만든다면, 사용자가 연결할 때 API로 구독하고(POST /api/v1/webhooks), 연결을 해제할 때 구독을 해지하고(DELETE /api/v1/webhooks/{id}), 이벤트 선택 화면에는 GET /api/v1/events 엔드포인트를, 연결 테스트에는 GET /api/v1/workspace 엔드포인트를 사용하세요. API 레퍼런스를 참고하세요.