イベントの通知を受け取るには、以下の手順に従います。
- モニターするイベントや解析するイベントペイロードを決定する
- ローカルサーバーで Webhook エンドポイントを HTTP エンドポイント (URL) として作成する
- 各イベントオブジェクトを解析し、レスポンスステータスコード 200 を返し、Smartpay からのリクエストを処理する
- Webhook エンドポイント作成 API エンドポイント使って公開 HTTPS URL を Smartpay に登録する
- Webhook のセキュリティーを確保する (推奨)
- Webhook エンドポイントを公開 HTTPS URL としてデプロイする
- Webhook エンドポイントが正しく動作しているかテストする
1. モニターするイベントを決定する
サポートされているイベントの種類を確認し、登録するイベントを決定します。イベントオブジェクトとイベントのサンプルコードを参照し、解析する必要があるオブジェクトの構造を確認します。
2. Webhook エンドポイントを作成する
Webhook エンドポイントの作成は、Web サイトでのページの作成と同様であり、URL を持つサーバー上の HTTPS エンドポイントです。ローカルマシンでエンドポイントを作成している場合は、HTTP でも構いませんが、本番環境では、HTTPS である必要があります。1つのエンドポイントで、複数の異なる種類のイベントを一度に処理することも、イベントごとに別々のエンドポイントを設定することも可能です。
3. Smartpay からのリクエストを処理する
受信するイベント通知の種類に応じてイベントオブジェクトを読み取るようにエンドポイントを設定する必要があります。Smartpay は、イベントを JSON ペイロードを持つ POST リクエストの一部として加盟店様の Webhook エンドポイントに送信します。
イベントオブジェクトを確認する
各イベントは、id
、type
、および関連する Smartpay リソースが data
の下にネストされたイベントオブジェクトとして構成されています (データ
の構造は、イベントが関連するオブジェクトであるため、イベントの種類によって異なります)。詳細については、イベントオブジェクトを参照してください。エンドポイントは、イベントの種類を確認し、各イベントのペイロードを解析する必要があります。
{
"id": "evt_test_yETRfprzCxFsSJaJZPIENt",
"object": "event",
"createdAt": 1664522079584,
"test": true,
"eventData": {
"type": "order.authorized",
"version": "2022-02-18",
"data": {...}
}
}
レスポンスコード 200 を返す
エンドポイントは、タイムアウトを引き起こす可能性がある複雑なロジックより前に、すばやく成功ステータスコード ( 200) を返す必要があります。
以下のサンプルコードでは、Webhook の署名を確認し (手順 5を参照)、HTTP ステータスコード 200 を返します。これらのサンプルコードは GitHub でも参照していただけます。
package main
import (
"bytes"
"encoding/json"
"log"
"io/ioutil"
"net/http"
"github.com/smartpay-co/sdk-go"
)
func webhook(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("Smartpay-Signature")
calculatedSignature := r.Header.Get("Calculated-Signature")
if signature != calculatedSignature {
log.Println("Signature verification failed.", signature, calculatedSignature)
w.WriteHeader(http.StatusBadRequest)
return
}
var bodyBytes []byte
var err error
if r.Body != nil {
bodyBytes, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Body reading error: %v", err)
return
}
defer r.Body.Close()
}
log.Printf("Headers: %+v\n", r.Header)
if len(bodyBytes) > 0 {
var prettyJSON bytes.Buffer
if err = json.Indent(&prettyJSON, bodyBytes, "", "\t"); err != nil {
log.Printf("JSON parse error: %v", err)
return
}
log.Println(string(prettyJSON.Bytes()))
} else {
log.Printf("Body: No Body Supplied\n")
}
w.Write([]byte("OK"))
}
func main() {
mux := http.NewServeMux()
webhookHandler := http.HandlerFunc(webhook)
mux.Handle("/", smartpay.CalculateWebhookSignatureMiddleware("YOUR_SIGNING_KEY", webhookHandler))
log.Print("Listening on :3000...")
err := http.ListenAndServe("0.0.0.0:3000", mux)
log.Fatal(err)
}
const crypto = require("crypto");
const express = require("express");
const createError = require("http-errors");
const Smartpay = require("@smartpay/sdk-node").default; // The Nodejs SDK
const app = express();
const secret = "YOUR_SIGNING_KEY";
const log = new Map();
app.use(
express.json({
verify: Smartpay.expressWebhookMiddleware(secret),
})
);
app.post("/webhooks", async (req, res, next) => {
const event = req.body;
const signature = req.headers["smartpay-signature"];
const calculatedSignature = req.headers["calculated-signature"];
console.log(req.headers);
console.log(event);
console.log(calculatedSignature);
if (signature === calculatedSignature) {
const key = req.headers["smartpay-event-id"];
const existing = log.get(key) || {
count: 0,
};
console.log(`key: ${key} count: ${existing.count}`);
if (existing && existing.count >= 2) {
log.delete(key);
res.send("");
return;
}
log.set(key, { count: existing.count + 1 });
}
next(createError.BadRequest());
});
app.listen(3000, "0.0.0.0", () =>
console.log("Node server listening on port 3000!")
);
<?php
require __DIR__ . '/./vendor/autoload.php';
use Tuupola\Base62;
$base62 = new Base62(["characters" => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789']);
$secret = 'YOUR_SIGNING_KEY';
$app = new \Slim\App;
$app->map(['get', 'post'], '/', function ($request, $response, $args) use (&$base62, &$secret) {
$fileName = trim($request->getUri()->getBasePath(), '/');
if ($fileName == 'webhooks') {
$headers = $request->getHeaders();
$smartpaySignature = $request->getHeader('Smartpay-Signature')[0];
$smartpaySignatureTimestamp = $request->getHeader('Smartpay-Signature-Timestamp')[0];
if ($smartpaySignature && $smartpaySignatureTimestamp) {
$body = $request->getBody();
$parsedBody = $request->getParsedBody();
$calculatedSignature = hash_hmac('sha256', $smartpaySignatureTimestamp . "." . $body, $base62->decode($secret));
print_r($calculatedSignature);
print_r($headers);
print_r($parsedBody);
if ($smartpaySignature == $calculatedSignature) {
return $response;
}
}
return $response->withStatus(400);
} else {
return $response->withStatus(404);
}
});
$app->run();
import os
from flask import Flask, request
from smartpay import Smartpay
SIGNING_SECRET = 'YOUR_SIGNING_KEY'
SECRET_KEY = os.environ.get('SECRET_KEY', '<YOUR_SECRET_KEY>')
PUBLIC_KEY = os.environ.get('PUBLIC_KEY', '<YOUR_PUBLIC_KEY>')
smartpay = Smartpay(SECRET_KEY, public_key=PUBLIC_KEY)
app = Flask(__name__, static_url_path='')
root = '../client/build'
@app.route("/webhooks", methods=['POST'])
def webhooks():
signature = request.headers['smartpay-signature']
verified = smartpay.verify_webhook_signature(data=request.get_data(), secret=SIGNING_SECRET, signature=signature)
if verified:
print("processing webhook event")
return ''
return '', 400
require 'sinatra'
require 'base_x'
set :port, 3000
secret = 'YOUR_SIGNING_KEY'
helpers do
def request_headers
env.inject({}){|acc, (k,v)| acc[$1.downcase] = v if k =~ /^http_(.*)/i; acc}
end
end
post '/webhooks' do
content_type :json
signature = request_headers["smartpay_signature"]
signature_timestamp = request_headers["smartpay_signature_timestamp"]
raw_body = request.body.read
body = JSON.parse raw_body
p BaseX::Base62ULD.decode(secret).bytes
hmac = OpenSSL::HMAC.new(BaseX::Base62ULD.decode(secret), OpenSSL::Digest::SHA256.new)
hmac.update signature_timestamp
hmac.update '.'
hmac.update raw_body
calculated_signature = hmac.hexdigest
p request_headers
p body
p calculated_signature
if signature == calculated_signature
return '', 200
end
return '', 400
rescue => err
if err.respond_to?(:response)
err.response.body
else
raise err
end
end
組み込みの再試行
Smartpay の Webhook には、3xx、4xx、または 5xx のレスポンスステータスコードの再試行があらかじめ組み込まれています。Webhook リクエストに 200 以外で 応答した場合、Smartpay はエクスポネンシャルバックオフ方式でリクエストの再試行を続けます。
4. Webhook エンドポイントを登録する
新しく作成した Webhook エンドポイントを Smartpay に登録し、登録するイベントを受信します。これは、Webhook エンドポイント作成 API エンドポイントを利用して行います。
5. Webhook のセキュリティーを確保する (推奨)
Webhook 署名を使用して、Webhook リクエストを作成したのは Smartpay であり、 Smartpay を模倣したサーバーからのものではないことを確認します。
Smartpay は、各イベントのSmartpay-Signature
ヘッダーに署名を含めることにより、加盟店様のエンドポイントに送信する Webhook イベントへの署名を行います。これにより、サードパーティではなく Smartpay が送信したイベントであることを確認していただけます。署名は、弊社の SDK を使って、または貴社ソリューションを使って手動で確認していただくことも可能です。
署名を確認する前に、エンドポイントのsigningSecret
を取得する必要があります。Smartpay は、各エンドポイントに一意の signingSecret
を生成し、Webhook エンドポイントを登録する際 (手順 4 を参照) や、以前登録した Webhook エンドポイントを取得する際に返します。