{"name":"GST Cranes Webhooks","documentation_url":"https://gstcranes.com/webhooks.md","register_url":"https://gstcranes.com/api/webhooks/endpoints","test_url":"https://gstcranes.com/api/webhooks/test","branded_signature_header":"GST-Webhook-Signature","signature_headers":["GST-Webhook-Signature","gst-webhook-signature"],"events":[{"type":"webhook.test","description":"Sent when a signed-in member requests a test delivery for a registered endpoint."},{"type":"saved_alert.match","description":"Best-effort companion delivery when a newly published marketplace listing matches one of the member saved alerts and the saved-alert notification fan-out reaches that alert."}],"signing":{"algorithm":"HMAC-SHA256","secret_format":"whsec_<random endpoint secret shown once at creation>","header":"GST-Webhook-Signature","branded_header":"GST-Webhook-Signature","lowercase_header":"gst-webhook-signature","official_sdk_verifiers":[{"language":"TypeScript","ecosystem":"npm","package_name":"@gstcranes/gstcranes","package_url":"https://www.npmjs.com/package/@gstcranes/gstcranes","install":"npm install @gstcranes/gstcranes","import_name":"verifyGstWebhookSignature","function_name":"verifyGstWebhookSignature"}],"sent_header_names":["gst-webhook-id","gst-webhook-event","gst-webhook-timestamp","gst-webhook-signature"],"signature_version":"v1","signature_base_string":"timestamp + \".\" + raw_body","timestamp_header":"GST-Webhook-Timestamp","id_header":"GST-Webhook-Id","format":"v1=<hex digest of timestamp.raw_body>","signed_payload":"GST-Webhook-Timestamp + \".\" + raw_body","raw_body_requirement":"Verify the raw request body bytes before JSON parsing. Do not reserialize parsed JSON to verify a delivery.","do_not_reserialize_json":true,"timestamp_tolerance_seconds":300,"unsigned_delivery_policy":"GST Cranes never sends unsigned webhook deliveries.","replay_protection":"Reject duplicate GST-Webhook-Id values per endpoint.","replay_store_requirement":"Store processed GST-Webhook-Id values per endpoint and reject repeats before processing.","verification_steps":["Read the raw request body before JSON parsing.","Do not reserialize parsed JSON; JSON key order and whitespace must not be reconstructed.","Reject unsigned deliveries before parsing the JSON payload.","Reject GST-Webhook-Timestamp values outside 300 seconds of the receiver clock.","Compute HMAC-SHA256 over GST-Webhook-Timestamp + \".\" + raw_body using the endpoint secret.","Compare the expected v1 digest to GST-Webhook-Signature with a constant-time compare.","Reject duplicate GST-Webhook-Id values per endpoint for replay protection."],"node_example":"const expected = 'v1=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');\nconst a = Buffer.from(expected);\nconst b = Buffer.from(signature);\nif (a.length !== b.length || !crypto.timingSafeEqual(a, b)) throw new Error('bad signature');"}}