APIFuseAPIFuseDocs

Getting started

  • Overview
  • Quickstart

Authentication

  • Managed keys
  • Scopes

Errors

  • Error model
  • Retries

Webhooks

  • Overview
  • Signatures

API reference

  • Amazon Japan
  • Baemin Provider
  • Buyee
  • CatchTable Restaurant Search and Reservations
  • Charan Commerce
  • Daangn public listings
  • Daiso Product and Store Data
  • Danawa price comparison
  • Demaecan
  • Ekitan
  • Goodchoice Domestic Lodging
  • Google Flights
  • Han River Water Level
  • Hot Pepper Gourmet provider
  • Hyundai Card
  • Jalan
  • Japan National Diet Minutes
  • Japan Disaster Alerts
  • Japan EDINET Filings
  • Japan e-Gov Law
  • Japan e-Stat
  • Japan GSI Geocoding
  • Japan e-Gov Open Data
  • Japan Post ZIP
  • Japan Public Holidays
  • JMA Weather
  • KakaoMap Place Search and Directions
  • Kakao T Taxi Dispatch
  • Korea Address Search
  • AirKorea Real-time Air Pollution
  • Korea Apartment Rent Prices
  • Korea Apartment Sale Prices
  • Korea Bid Notices
  • Korea Building Register
  • Korea Business Verify
  • Korea Camping
  • Korea DART Corporate Finance
  • DART Corporate Info
  • Korea Culture Events
  • Korea Disaster Alert
  • Korea Emergency Hospital
  • Korea ETF
  • Korea EV Charger
  • Korea Fuel Price
  • Korea Holiday
  • Korea Hospital Info
  • Korea Land Price
  • Korea MFDS Drug Safety
  • Korea MFDS Food Safety
  • Korea Household Waste Disposal Guide
  • Korea National Law Search and Lookup
  • Korea NEIS School Meals
  • Carrier List and Delivery Tracking
  • Korea Pharmacy
  • Korea Population
  • Korea Stock Index
  • Korea Stock Price
  • Korea Train Schedule
  • Korea Weather Forecast Data
  • Korea Weather Forecast
  • Korea Welfare Service
  • K-Startup
  • LH Housing Notices
  • Market Kurly product data
  • Mercari
  • Modu Parking
  • Naver Blog Search
  • Naver Flight API
  • Naver Map
  • Naver News Search
  • NOL Stays
  • Ohouse Store and Contents
  • Rakuten Ichiba
  • Rakuten Travel
  • SEC EDGAR Filings
  • Seoul Bike
  • Seoul Live Crowd Density
  • Seoul Subway Arrivals
  • Shinhan Bank
  • Shinhan Card
  • Skiplagged
  • SUUMO
  • Swing Taxi
  • Tabelog
  • TableCheck
  • Weverse Provider
  • Yahoo! Shopping (Japan)
  • Yogiyo
  • ZOZOTOWN

Guides

  • APIFuse Docs
  • Getting started
  • Authentication and Connections
  • Playground
  • OpenAPI and schemas
  • MCP endpoint
  • Developer MCP guide
  • Schema bundles
  • Next.js App Router integration
  • FastAPI integration
  • Error Handling
  • FAQ
  • Resources

Changelog

  • Changelog
APIFuseAPIFuse
DocsServicesPlaygroundStatus
Log inSign up

Webhooks

Signatures

Verify the signature of every webhook payload you receive.

A webhook endpoint is a URL open to the internet, and a plausible looking body proves nothing. An unverified payload could be forged, tampered with or replayed, so verify the signature of every payload before you process it. The endpoint secret you verify with starts with whsec_ and is shown exactly once in the console when you register the endpoint.

The Frism-Signature header

Every delivery carries a Frism-Signature header. The value is a list of key=value pairs separated by ,.

Headers of a delivery
POST /webhooks/frism HTTP/1.1
Host: example.com
Content-Type: application/json
Frism-Event-Id: evt_01J8QH5R7T
Frism-Signature: t=1788257727,v2=d7e16839f487db479fa277fb11c2d6139225ea837c672df8a68e0452fd7ea772,v1=59ba753f14e68fd9aff609e623a55afd67db964cc881d66cb7aa09e3ef65ad58
PartMeaning
tWhen the signature was produced, in unix seconds. Reject events older than 5 minutes.
v2Hex HMAC-SHA256 digest of the string <t>.<raw body>, keyed with the endpoint secret. This is the value you verify.
v1Legacy digest over the raw body alone. Sent alongside v2 until 2026-09-30 only; do not use it in new code.

v1 carries no timestamp, so an intercepted payload replayed later still passes. Trust v2 only, and see the changelog for the v1 shutdown schedule.

Verification steps

  1. Split the Frism-Signature header on , and parse out t and v2.
  2. Check that t is within 5 minutes of the current time. Reject otherwise.
  3. Build <t>.<raw body> from the raw body string exactly as it arrived, and compute the HMAC-SHA256 digest with the endpoint secret. It must be the received bytes, not a body re-serialized after JSON.parse.
  4. Compare the computed digest against v2 with a timing-safe comparison. Only on a match do you parse the JSON and start processing.

Because the digest is computed over t and the body joined with a dot, changing either one breaks the signature; that binding is what defeats replays. The 5 minute allowance on t exists to absorb delivery latency and clock drift, so keep your server clocks synchronized with NTP.

TypeScript verification example

The built-in node:crypto module is all you need. No external library is required.

Signature verification over the raw body
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 5 * 60;

export function verifyFrismSignature(
  rawBody: string,
  header: string,
  secret: string,
): boolean {
  // 1. Parse the header into key=value pairs.
  const parts = new Map<string, string>();
  for (const pair of header.split(",")) {
    const index = pair.indexOf("=");
    if (index > 0) parts.set(pair.slice(, index), pair.slice(index + ));
  }
   t = parts.get();
   v2 = parts.get();
   (t === undefined || v2 === undefined)  false;

  
   timestamp = Number(t);
   (!Number.isInteger(timestamp))  false;
   now = Math.floor(Date.now() / );
   (Math.abs(now - timestamp) > TOLERANCE_SECONDS)  false;

  
   expected = createHmac(, secret)
    .update(`${t}.${rawBody}`)
    .digest();

  
  
   received = Buffer.(v2, );
   (received.length !== expected.length)  false;
   timingSafeEqual(expected, received);
}

   handleFrismEvent(request: Request): Promise<Response> {
  
   rawBody =  request.text();
   header = request.headers.get() ?? ;
   secret = process.env.FRISM_WEBHOOK_SECRET ?? ;

   (!verifyFrismSignature(rawBody, header, secret)) {
    
      Response(null, { status:  });
  }

   event = JSON.parse(rawBody) as { id: string; : string };
  
  console.log(`verified ${event.} (${event.id})`);
    Response(null, { status:  });
}

Common mistakes

MistakeWhat goes wrong
JSON.parse before verifyingA string re-serialized after parsing differs in whitespace and key order, so the digest never matches. Touching unverified data is a risk in itself. Always verify the raw body string.
Comparing digests with ===Plain string comparison takes a different amount of time depending on where it fails, which leaks timing information. Use timingSafeEqual.
Checking v1 instead of v2v1 has no timestamp, so it cannot stop a replay of an old payload, and it stops being sent after 2026-09-30.

When verification fails

Respond 400 and do not process the payload. Answering 2xx to a failed verification would record the delivery as successful and stop the retries, so failures must surface as 4xx. Log the Frism-Event-Id and the failure reason, but treat the body as untrusted. Even if a deployment mistake makes you reject genuine events, APIFuse , so the events come back once your verifier is fixed.

Testing locally

Signing a request yourself with openssl lets you check the verifier before deploying. The body below is a reservation.confirmed event carrying the . Vary T or BODY in the script to confirm the handler rejects a mismatched digest and a stale timestamp alike.

Craft and send a signed delivery
SECRET="whsec_mJq82LkabQfw"   # the endpoint secret from the console
BODY='{"id":"evt_01J8QH5R7T","type":"reservation.confirmed","created_at":"2026-09-01T10:15:27Z","data":{"object":{"id":"rsv_01J8QDGT2K","status":"confirmed","restaurant_id":"res_01J8Q9W3TE","starts_at":"2026-09-02T19:00:00+09:00","party_size":2}}}'
T=$(date +%s)
V2=$(printf "%s.%s" "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST http://localhost:3000/webhooks/frism \
  -H "Content-Type: application/json" \
  -H "Frism-Event-Id: evt_01J8QH5R7T" \
  -H "Frism-Signature: t=$T,v2=$V2" \
  --data "$BODY"
The envelope shape, the event catalogue and dedupe are covered in the .
0
1
const
"t"
const
"v2"
if
return
// 2. Check the timestamp is within 5 minutes.
const
if
return
const
1000
if
return
// 3. Compute the expected digest over "<t>.<raw body>".
const
"sha256"
// 4. Timing-safe compare. timingSafeEqual throws on length
// mismatch, so filter that first.
const
from
"hex"
if
return
return
export
async
function
// Take the raw body first. JSON.parse happens only after verification.
const
await
const
"Frism-Signature"
""
const
""
if
// Verification failed: respond 400 and do not process.
return
new
400
const
type
// From here on the event is trustworthy.
type
return
new
200
retries for 24 hours
reservation resource
webhooks overview