コンテンツへスキップ

デベロッパー

WebhookとZapier

Webhookは、アプリが一時停止されたときやスマートリンクがクリックされたときなど、ワークスペースで何かが起きた瞬間に、お客様のサーバーへHTTPリクエストを送信します。自社システムの更新やSlackへの投稿に利用したり、サーバーを用意せずにmapps.oneをZapier、Make、n8nに接続したりできます。WebhookはStudioプランで利用でき、ワークスペースあたり最大10個のエンドポイントを設定できます。

エンドポイントを追加する

  1. ダッシュボードでデベロッパー → Webhookを開き、エンドポイントを追加を選択します。この操作は、ワークスペースのオーナーと管理者が行えます。
  2. 公開されているhttps URLを入力し、送信するイベント(またはすべてのイベント)を選んで、説明を追加します。
  3. 署名シークレットをコピーし、サーバーの他のシークレットと一緒に保管します。
  4. テストイベントを送信を選択して、エンドポイントが応答することを確認します。

エンドポイントのページには最近の配信が表示され、レスポンスのステータス、レスポンス本文の最初の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-Typeapplication/json
User-Agentmapps.one-Webhooks/1.0
X-Mapps-Eventイベントタイプ(例:link.clicked)
X-Mapps-DeliveryイベントID。再試行しても同じ値のままです。
X-Mapps-Timestampこの配信試行のUnix時間
X-Mapps-Signaturet={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から送信され、途中で改ざんされていないことを確かめるため、すべてのリクエストで署名をチェックしてください。

  1. X-Mapps-Signatureヘッダーからtとv1を読み取ります。
  2. エンドポイントの署名シークレットを使用して、{t}.{raw request body}のHMAC-SHA256を小文字の16進数で計算します。
  3. 定数時間の比較を使用して、計算結果をv1と比較します。
  4. リプレイ攻撃を防ぐため、tがサーバーの時刻から5分以上ずれている場合はリクエストを拒否します。
未加工の本文を使用してください。JSONを解析する前に、受信したバイト列そのものに対して署名を計算してください。JSONを再エンコードすると、スラッシュやUnicode文字の表現が変わり、署名が一致しなくなることがあります。

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

  1. Webhooks by ZapierをトリガーとしてZapを作成し、Catch Hookを選択します。
  2. Zapierが発行するWebhook URLをコピーします。
  3. mapps.oneで、そのURLを使ってエンドポイントを追加し、app.status_changedやanalytics.daily_summaryなど、必要なイベントを選択します。
  4. 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リファレンスをご覧ください。