デベロッパー
WebhookとZapier
Webhookは、アプリが一時停止されたときやスマートリンクがクリックされたときなど、ワークスペースで何かが起きた瞬間に、お客様のサーバーへHTTPリクエストを送信します。自社システムの更新やSlackへの投稿に利用したり、サーバーを用意せずにmapps.oneをZapier、Make、n8nに接続したりできます。WebhookはStudioプランで利用でき、ワークスペースあたり最大10個のエンドポイントを設定できます。
エンドポイントを追加する
- ダッシュボードでデベロッパー → Webhookを開き、エンドポイントを追加を選択します。この操作は、ワークスペースのオーナーと管理者が行えます。
- 公開されている
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 | 各アプリの前日のクリック合計。1日1回送信されます。 |
リクエストの形式
すべてのイベントは、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} |
本文は、すべてのイベントで同じ構造のエンベロープです。
{
"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が発行するWebhook URLをコピーします。
- mapps.oneで、そのURLを使ってエンドポイントを追加し、
app.status_changedやanalytics.daily_summaryなど、必要なイベントを選択します。 - mapps.oneでテストイベントを送信を選択し、ZapierでTest triggerを選択します。これで、
data.app.nameなどペイロードのフィールドを次のステップで使えるようになります。
活用例:アプリが一時停止されたときにSlackにメッセージを投稿する、毎日のクリック数をGoogleスプレッドシートに追加する、クライアントのドメインが確認されたときにクライアントへメールを送る。
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リファレンスをご覧ください。