Skip to main content

ポイント付与API

ポイント付与API

概要

いつ呼ぶか / 何をするか

  • SurveyGate側から、モニターへのポイント付与を通知するAPIです
  • アンケート回答完了後、SurveyGate側から通知されます
  • メディア側はこの通知を受けて、自社システムでのポイント反映処理を行います

重複仕様(べき等性)
なぜ重複対策が必要か
・ネットワーク障害時に再送信が発生する場合があります
・5xxエラー時は自動リトライが実施されます
そのため、同一 reward_no × monitor_id は1度のみ処理してください。

  • 重複の定義: points配列内の1レコードを識別するキーは reward_no × monitor_id です。同じ組み合わせが2回以上通知された場合は「重複通知」とみなします(reward_request_idが異なっていても同一と扱います)。
  • 期待する挙動: 重複通知が来てもエラーとして返さず、「すでに処理済み」として成功レスポンスを返してください。実際のポイント付与処理は、未処理の reward_no × monitor_id に対してのみ行ってください。
  • 実装方法のポイント: 重複チェックはメモリ変数ではなく、DBやRedisなどの永続化ストレージに保存してください。サーバー再起動や複数台構成でも、「あるreward_no × monitor_idが処理済みかどうか」が失われないようにする必要があります。
  • リトライとの関係: 一時的なエラー(5xxなど)が発生した場合、SurveyGate側で同じreward_request_id・同じpoints内容のリクエストが自動リトライされます。メディア側では上記の重複仕様に基づき、reward_no × monitor_id単位でべき等に処理することで、リトライやネットワーク障害時でも二重付与を防止できます。

認証

X-API-Key: {api_key}

  • ヘッダー名: X-API-Key
  • 値: {point_notify_api_key}
  • 失敗時の扱い: 401 Unauthorizedを返却

サンプルコード(Node.js):
※エンドポイント内で req / res が利用できる前提の断片です。

const crypto = require('crypto');

const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({
errors: [{ code: 2101, message: "認証キーが不正です" }]
});
}

// 定数時間比較(タイミング攻撃対策)。両 Buffer の長さを揃えてから timingSafeEqual を使うこと
const expectedApiKey = process.env.POINT_NOTIFY_API_KEY ?? '';
if (!expectedApiKey) {
return res.status(401).json({
errors: [{ code: 2101, message: "認証キーが不正です" }]
});
}
const apiKeyBuf = Buffer.from(apiKey, 'utf8');
const expectedBuf = Buffer.from(expectedApiKey, 'utf8');
if (apiKeyBuf.length !== expectedBuf.length || !crypto.timingSafeEqual(apiKeyBuf, expectedBuf)) {
return res.status(401).json({
errors: [{ code: 2101, message: "認証キーが不正です" }]
});
}

サンプルコード(PHP):

$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
if (empty($apiKey)) {
http_response_code(401);
echo json_encode([
'errors' => [[
'code' => 2101,
'message' => '認証キーが不正です'
]]
]);
exit;
}

// 定数時間比較のため hash_equals を使用すること
$expectedApiKey = getenv('POINT_NOTIFY_API_KEY');
if ($expectedApiKey === false || $expectedApiKey === '') {
http_response_code(401);
echo json_encode([
'errors' => [[
'code' => 2101,
'message' => '認証キーが不正です'
]]
]);
exit;
}
if (!hash_equals($expectedApiKey, $apiKey)) {
http_response_code(401);
echo json_encode([
'errors' => [[
'code' => 2101,
'message' => '認証キーが不正です'
]]
]);
exit;
}

エンドポイント

POST {point_notify_url}

  • URLはメディア側からSurveyGate側に提供する値です
  • メディア側で実装したポイント付与APIのエンドポイントURLをSurveyGate側にご提供ください

リクエスト仕様

リクエストヘッダー:

ヘッダー名必須説明
Content-Typeapplication/jsonJSON形式
X-API-Key{api_key}認証用APIキー

リクエストボディ:

#パラメータ名論理名データ型サイズ必須説明
1reward_request_id謝礼リクエストID文字列固定36SurveyGate側で生成する一意のポイント付与リクエストID(UUIDv7形式)
2request_dateリクエスト日時日時固定19yyyy-MM-dd HH:mm:ss
3pointsポイント通知配列配列-ポイント通知の配列(後述)

points配列の各要素:

#パラメータ名論理名データ型サイズ必須説明
1reward_no謝礼付与ID文字列可変101件のポイント謝礼付与明細を識別するID(重複チェックキー)
2monitor_idモニターID文字列可変32メディア側でモニターを一意に判別するID(重複チェックキー)
3point付与ポイント数値可変8モニターに付与されるポイント
4enquete_idアンケートID文字列可変10どのアンケートに対するポイントか識別するためのID
5media_fee_flgフィー発生有無数値固定1フィー発生フラグ(0:なし, 1:あり)
6point_history_textポイント履歴表示名文字列可変300モニターのポイント履歴に表示されるテキスト

リクエスト例:

{
"reward_request_id": "018e1234-5678-7890-abcd-ef1234567890",
"request_date": "2026-02-01 10:00:00",
"points": [
{
"reward_no": "reward001",
"monitor_id": "monitor0000000001",
"point": 60,
"enquete_id": "enquete_001",
"media_fee_flg": 1,
"point_history_text": "ベーシック調査に関するアンケート"
},
{
"reward_no": "reward002",
"monitor_id": "monitor0000000002",
"point": 100,
"enquete_id": "enquete_002",
"media_fee_flg": 0,
"point_history_text": "日常生活に関するアンケート"
},
{
"reward_no": "reward003",
"monitor_id": "monitor0000000003",
"point": 50,
"enquete_id": "enquete_003",
"media_fee_flg": 1,
"point_history_text": "電化製品に関するアンケート"
},
{
"reward_no": "reward001",
"monitor_id": "monitor0000000004",
"point": 300
}
]
}

JSON形式の説明:

  • points配列には、1件のポイント通知データが1要素として格納されます
  • 任意項目(media_fee_flgpoint_history_text)は、値が存在する場合のみ含めます

レスポンス仕様(成功/失敗)

SurveyGate側では、メディアのAPIレスポンスについてHTTPステータスコードのみを参照します。レスポンスボディやヘッダーの形式は指定しません。メディア側では、ご利用のフレームワークや既存APIの形式に合わせて実装して問題ありません。

メディア側では、処理結果に応じて適切なステータスコードを返してください(下表のすべてを実装する必要はありません)。SurveyGate側では、受け取ったステータスコードに応じて以下のように扱います。

重要:

  • SurveyGate側はHTTPステータスコードのみを参照します。レスポンスボディの内容は参照しません。

 2xx → 正常終了
 4xx/5xx → エラー扱い(リトライ対象)

HTTPステータスコードの扱い:

HTTPステータスメッセージ
2xx(例:200 OK)正常終了として扱い、リトライしません
4xx・5xx 等(例:400, 401, 404, 500, 503, タイムアウト・ネットワークエラー含む)エラーとして扱い、リトライを行います(即時リトライの後、一定期間・条件で再送信する場合があります)

サンプルコード

サンプルコード(Node.js):

const express = require('express');
const app = express();

app.use(express.json());

app.post('/api/points/notify', async (req, res) => {
const apiKey = req.headers['x-api-key'];
if (apiKey !== process.env.POINT_NOTIFY_API_KEY) {
return res.status(401).json({
errors: [{ code: 2101, message: "認証キーが不正です" }]
});
}

const { reward_request_id, request_date, points } = req.body ?? {};
if (!reward_request_id || request_date == null || request_date === '' || !Array.isArray(points)) {
return res.status(400).json({
errors: [
{
code: 2003,
message: `リクエストパラメータが不正です。reward_request_id: ${reward_request_id ?? ""}`,
},
]
});
}

for (const point of points) {
const { reward_no, monitor_id, point: amount, enquete_id, monitor_enquete_end_flg } = point ?? {};

// 重複チェック: (reward_no + monitor_id) を永続化ストレージで確認すること
const isProcessed = await checkDuplicateInDB(reward_no, monitor_id);
if (isProcessed) {
continue;
}

// 【実装箇所】ポイント履歴の保存・残高更新・処理済み記録
await savePointToDB(monitor_id, amount, reward_no, enquete_id, monitor_enquete_end_flg);
}

res.status(200).json({
status: "success",
reward_request_id: reward_request_id
});
});

// 以下はダミー。実装時にDB確認・保存処理に置き換えること。
async function checkDuplicateInDB(rewardNo, monitorId) { /* DB確認処理 */ return false; }
async function savePointToDB(monitorId, amount, rewardNo, enqueteId, monitorEnqueteEndFlg) { /* DB保存処理(および処理済み記録の保存) */ }

app.listen(3000, () => console.log('Server running on port 3000'));

サンプルコード(PHP):

<?php

// 本番ではエラーハンドリング・Content-Type を適切に設定すること
header('Content-Type: application/json');

$input = json_decode(file_get_contents('php://input'), true) ?? [];

$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
if ($apiKey !== getenv('POINT_NOTIFY_API_KEY')) {
http_response_code(401);
echo json_encode(['errors' => [['code' => 2101, 'message' => '認証キーが不正です']]]);
exit;
}

if (empty($input['reward_request_id']) || !isset($input['request_date']) || $input['request_date'] === '' || !isset($input['points']) || !is_array($input['points'])) {
http_response_code(400);
$rid = $input['reward_request_id'] ?? '';
echo json_encode(['errors' => [['code' => 2003, 'message' => 'リクエストパラメータが不正です。reward_request_id: ' . $rid]]]);
exit;
}

$points = $input['points'];
foreach ($points as $point) {
$rewardNo = $point['reward_no'] ?? null;
$monitorId = $point['monitor_id'] ?? null;
$amount = $point['point'] ?? null;
$enqueteId = $point['enquete_id'] ?? null;
$monitorEnqueteEndFlg = $point['monitor_enquete_end_flg'] ?? null;

// 重複チェック: (reward_no + monitor_id) を永続化ストレージで確認すること
if (isAlreadyProcessed($rewardNo, $monitorId)) {
continue;
}

// 【実装箇所】ポイント履歴の保存・残高更新・処理済み記録
grantPoint($monitorId, $amount, $rewardNo, $enqueteId, $monitorEnqueteEndFlg);
}

http_response_code(200);
echo json_encode([
'status' => 'success',
'reward_request_id' => $input['reward_request_id']
]);

// 以下はダミー。実装時にDB確認・保存処理に置き換えること。
function isAlreadyProcessed($rewardNo, $monitorId) { /* DB確認処理の実装 */ return false; }
function grantPoint($monitorId, $amount, $rewardNo, $enqueteId = null, $monitorEnqueteEndFlg = null) { /* DB保存処理(および処理済み記録の保存)の実装 */ }
?>