X-SIGNATURE が含まれており、Waffo の秘密鍵で署名されています。加盟店は Waffo の公開鍵を使用して署名を検証する必要があります。
Waffo 公開鍵の取得
マーチャントポータル → Integration メニューにログインすると、Waffo 公開鍵を確認してコピーできます。このページにアクセスするには、Dev または Admin ロールの権限が必要です。
推奨アプローチ: SDK を使用する
SDK のhandleWebhook() メソッドは、署名検証、イベント解析、ルーティング、応答の署名を自動的に実行します。
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const body = req.body.toString();
const signature = req.headers['x-signature'] as string;
const result = await waffo.webhook().handleWebhook(body, signature);
res.setHeader('X-SIGNATURE', result.responseSignature);
res.setHeader('Content-Type', 'application/json');
res.status(200).send(result.responseBody);
});
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-SIGNATURE")
result := handler.HandleWebhook(string(body), signature)
w.Header().Set("X-SIGNATURE", result.ResponseSignature)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write([]byte(result.ResponseBody))
}
手動での検証
SDK を使用せず手動で処理する必要がある場合、検証手順は以下のとおりです。1
署名の取得
リクエストヘッダーから
X-SIGNATURE を取得します。2
生のボディの取得
生のリクエストボディ文字列を取得します (JSON のパース後に再度文字列化しないでください)。
3
署名の検証
Waffo 公開鍵と SHA256WithRSA を使用して署名を検証します。
4
イベントの処理
署名検証が通ったあとにイベントを処理します。
5
応答の署名
応答に
X-SIGNATURE ヘッダーを設定する必要があります (加盟店の秘密鍵で応答ボディを署名してください)。手動実装の例
import { createVerify, createSign } from 'crypto';
function verifyWaffoSignature(body: string, signature: string): boolean {
const verify = createVerify('SHA256');
verify.update(body);
return verify.verify(process.env.WAFFO_PUBLIC_KEY!, signature, 'base64');
}
function signResponse(responseBody: string): string {
const sign = createSign('SHA256');
sign.update(responseBody);
return sign.sign(process.env.MERCHANT_PRIVATE_KEY!, 'base64');
}
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const body = req.body.toString();
const signature = req.headers['x-signature'] as string;
if (!verifyWaffoSignature(body, signature)) {
const failedBody = JSON.stringify({ message: 'failed' });
res.setHeader('X-SIGNATURE', signResponse(failedBody));
return res.status(200).send(failedBody);
}
// Process the event...
const successBody = JSON.stringify({ message: 'success' });
res.setHeader('X-SIGNATURE', signResponse(successBody));
res.status(200).send(successBody);
});
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"encoding/json"
"io"
"net/http"
"os"
)
// Verify the Waffo signature
func verifyWaffoSignature(body string, signature string) bool {
pubKeyPEM := []byte(os.Getenv("WAFFO_PUBLIC_KEY"))
block, _ := pem.Decode(pubKeyPEM)
pubKey, _ := x509.ParsePKIXPublicKey(block.Bytes)
sig, _ := base64.StdEncoding.DecodeString(signature)
hash := sha256.Sum256([]byte(body))
err := rsa.VerifyPKCS1v15(pubKey.(*rsa.PublicKey), crypto.SHA256, hash[:], sig)
return err == nil
}
// Sign the merchant response
func signResponse(responseBody string) string {
privKeyPEM := []byte(os.Getenv("MERCHANT_PRIVATE_KEY"))
block, _ := pem.Decode(privKeyPEM)
privKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
hash := sha256.Sum256([]byte(responseBody))
sig, _ := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256, hash[:])
return base64.StdEncoding.EncodeToString(sig)
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-SIGNATURE")
if !verifyWaffoSignature(string(body), signature) {
failedBody, _ := json.Marshal(map[string]string{"message": "failed"})
w.Header().Set("X-SIGNATURE", signResponse(string(failedBody)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(failedBody)
return
}
// Process the event...
successBody, _ := json.Marshal(map[string]string{"message": "success"})
w.Header().Set("X-SIGNATURE", signResponse(string(successBody)))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(successBody)
}
注意事項
- イベントを処理する前に必ず署名を検証してください。先に応答を返してから検証するのは避けてください。
- 応答には必ず
X-SIGNATUREヘッダーを含めてください。含まれていない場合、Waffo は配信失敗として扱います。 - 署名検証には生のリクエストボディを使用し、JSON のパース後に再度文字列化しないでください。
- SDK は完全な Webhook 処理パイプラインを提供しています。手動での処理よりも SDK の利用を推奨します。