curl Node.js Python PHP Go Rust
# Send a signed test notification to your local endpoint
RECUSTOMER_WEBHOOK_SECRET = 'your_webhook_secret'
BODY = $( cat << 'JSON'
{
"order_number": "string",
"shipping_address": {
"phone_number": "09012345678"
},
"tracking_number": "string",
"estimated_delivery_date": "2024-01-15",
"actual_delivery_date": "2024-01-15",
"actual_delivery_datetime": "2024-01-15T10:00:00+09:00",
"latest_delivery_status": "荷物受付け",
"delivery_company": "string",
"logistics_centers": [
{
"center": {
"center_name": "string",
"center_address": "string",
"updated_at": "string",
"status": "string"
}
}
]
}
JSON
)
SIGNATURE = "sha256=$( printf '%s' " $BODY " | openssl dgst -sha256 -hmac " $RECUSTOMER_WEBHOOK_SECRET " | sed 's/^.* //')"
curl -X POST 'http://localhost:3000/your_shipping_endpoint' \
-H "X-Recustomer-Signature-256: $SIGNATURE " \
-H 'X-Recustomer-Sender-ID: 1bad6b8cf97131fceab8543e81f7757195fbb1d36b376ee994ad1cf17699c464' \
-H 'Content-Type: application/json' \
--data-binary " $BODY " // npm install express
import crypto from 'node:crypto' ;
import express from 'express' ;
const secret = process.env. RECUSTOMER_WEBHOOK_SECRET ;
if ( ! secret) throw new Error ( 'RECUSTOMER_WEBHOOK_SECRET is not set' );
// Compare in constant time so the response time does not reveal the expected value
const safeEqual = ( a , b ) => {
const x = Buffer. from (a);
const y = Buffer. from (b);
return x. length === y. length && crypto. timingSafeEqual (x, y);
};
const app = express ();
// Verify the signature against the raw body, before parsing it as JSON
app. post (
"/your_shipping_endpoint" ,
express. raw ({ type: 'application/json' }),
( req , res ) => {
const expected = 'sha256=' + crypto. createHmac ( 'sha256' , secret). update (req.body). digest ( 'hex' );
if ( ! safeEqual (req. get ( "X-Recustomer-Signature-256" ) ?? '' , expected)) {
return res. sendStatus ( 401 );
}
const event = JSON . parse (req.body. toString ( 'utf8' ));
console. log (event);
res. status ( 200 ). json ({
"results" : "string"
});
},
);
app. listen ( 3000 ); # pip install flask
import hashlib
import hmac
import os
from flask import Flask, jsonify, request
SECRET = os.environ.get( "RECUSTOMER_WEBHOOK_SECRET" , "" )
if not SECRET :
raise RuntimeError ( "RECUSTOMER_WEBHOOK_SECRET is not set" )
app = Flask( __name__ )
@app.route ( "/your_shipping_endpoint" , methods = [ "POST" ])
def handle ():
# Verify the signature against the raw body, before parsing it as JSON
body = request.get_data()
expected = "sha256=" + hmac.new( SECRET .encode(), body, hashlib.sha256).hexdigest()
received = request.headers.get( "X-Recustomer-Signature-256" , "" )
if not hmac.compare_digest(received.encode(), expected.encode()):
return "" , 401
event = request.get_json()
print (event)
return jsonify({
"results" : "string" ,
}), 200
if __name__ == "__main__" :
app.run( port = 3000 ) <? php
// Run: php -S localhost:3000 handler.php (POST /your_shipping_endpoint)
$secret = getenv ( 'RECUSTOMER_WEBHOOK_SECRET' );
if ($secret === false || $secret === '' ) {
http_response_code ( 500 );
exit ( 'RECUSTOMER_WEBHOOK_SECRET is not set' );
}
// Verify the signature against the raw body, before parsing it as JSON
$body = file_get_contents ( 'php://input' );
$headers = [];
foreach ( getallheaders () as $name => $value) {
$headers[ str_replace ( '_' , '-' , strtolower ($name))] = $value;
}
$expected = 'sha256=' . hash_hmac ( 'sha256' , $body, $secret);
if ( ! hash_equals ($expected, $headers[ 'x-recustomer-signature-256' ] ?? '' )) {
http_response_code ( 401 );
exit ;
}
$event = json_decode ($body, true );
http_response_code ( 200 );
header ( 'Content-Type: application/json' );
echo <<< ' JSON '
{
"results" : "string"
}
JSON ; package main
import (
" crypto/hmac "
" crypto/sha256 "
" encoding/hex "
" encoding/json "
" io "
" log "
" net/http "
" os "
)
const responseBody = `{
"results": "string"
}`
func main () {
secret := os. Getenv ( "RECUSTOMER_WEBHOOK_SECRET" )
if secret == "" {
log. Fatal ( "RECUSTOMER_WEBHOOK_SECRET is not set" )
}
http. HandleFunc ( "POST /your_shipping_endpoint" , func ( w http . ResponseWriter , r * http . Request ) {
// Verify the signature against the raw body, before parsing it as JSON
body, err := io. ReadAll (r.Body)
if err != nil {
http. Error (w, err. Error (), http.StatusBadRequest)
return
}
mac := hmac. New (sha256.New, [] byte (secret))
mac. Write (body)
expected := "sha256=" + hex. EncodeToString (mac. Sum ( nil ))
received := r.Header. Get ( "X-Recustomer-Signature-256" )
if ! hmac. Equal ([] byte (received), [] byte (expected)) {
w. WriteHeader (http.StatusUnauthorized)
return
}
var event map [ string ] any
if err := json. Unmarshal (body, & event); err != nil {
http. Error (w, err. Error (), http.StatusBadRequest)
return
}
log. Println (event)
w. Header (). Set ( "Content-Type" , "application/json" )
w. WriteHeader ( 200 )
w. Write ([] byte (responseBody))
})
http. ListenAndServe ( ":3000" , nil )
} // Cargo.toml: axum = "0.8", tokio = { version = "1", features = ["full"] },
// hmac = "0.12", sha2 = "0.10", hex = "0.4"
use axum :: {
body :: Bytes ,
extract :: State ,
http :: {header, HeaderMap , StatusCode },
response :: { IntoResponse , Response },
routing :: post,
Router ,
};
use hmac :: { Hmac , Mac };
use sha2 :: Sha256 ;
const RESPONSE_BODY : & str = r#"{
"results": "string"
}"# ;
// Verify the signature against the raw body, before parsing it as JSON
async fn handle ( State (secret) : State < String >, headers : HeaderMap , body : Bytes ) -> Response {
let mut mac = Hmac :: < Sha256 > :: new_from_slice (secret . as_bytes ()) . expect ( "HMAC accepts any key length" );
mac . update ( & body);
let signature = headers
. get ( "X-Recustomer-Signature-256" )
. and_then ( | v | v . to_str () . ok ())
. and_then ( | v | v . strip_prefix ( "sha256=" ))
. and_then ( | v | hex :: decode (v) . ok ());
if ! signature . is_some_and ( | s | mac . verify_slice ( & s) . is_ok ()) {
return StatusCode :: UNAUTHORIZED . into_response ();
}
println! ( "{}" , String :: from_utf8_lossy ( & body));
( StatusCode :: OK , [( header :: CONTENT_TYPE , "application/json" )], RESPONSE_BODY ) . into_response ()
}
#[tokio :: main]
async fn main () {
let state = std :: env :: var ( "RECUSTOMER_WEBHOOK_SECRET" )
. ok ()
. filter ( | v | ! v . is_empty ())
. expect ( "RECUSTOMER_WEBHOOK_SECRET is not set" );
let app = Router :: new () . route ( "/your_shipping_endpoint" , post (handle)) . with_state (state);
let listener = tokio :: net :: TcpListener :: bind ( "0.0.0.0:3000" ) .await. unwrap ();
axum :: serve (listener, app) .await. unwrap ();
}