Verify a Webhook#
Use this when your service needs signed operation lifecycle events.
Prerequisites#
- A public HTTPS endpoint.
- Raw request-body access in your server framework.
- Storage for the signing secret returned once on create or rotate.
Create an Endpoint#
curl -sS https://api.hightop.com/v1/agent/webhooks \
-H "content-type: application/json" \
-H "x-agent-id: $HIGHTOP_AGENT_ID" \
-H "x-api-key: $HIGHTOP_API_KEY" \
-H "Idempotency-Key: webhook-create-$(uuidgen)" \
-d '{
"url": "https://example.com/hightop/webhook",
"description": "Production lifecycle webhook",
"event_types": ["payment.executed", "payment.execution_failed", "conversion.executed", "conversion.execution_failed"]
}'Store signing_secret immediately. It is shown once.
Verify Signatures#
Hightop signs:
${timestamp}.${raw_body}Use the raw request body, not parsed JSON.
import crypto from 'crypto'
export function verifyHightopWebhook(secret: string, timestamp: string, rawBody: string, signature: string) {
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const expectedBuffer = Buffer.from(expected, 'hex')
return signature.split(',').some((part) => {
const [version, value] = part.trim().split('=')
if (version !== 'v1' || !value) return false
const actualBuffer = Buffer.from(value, 'hex')
return actualBuffer.length === expectedBuffer.length && crypto.timingSafeEqual(actualBuffer, expectedBuffer)
})
}Reject timestamps outside your tolerance window, usually 5-15 minutes.
Test the Endpoint#
curl -sS https://api.hightop.com/v1/agent/webhooks/$WEBHOOK_ID/test \
-H "content-type: application/json" \
-H "x-agent-id: $HIGHTOP_AGENT_ID" \
-H "x-api-key: $HIGHTOP_API_KEY" \
-H "Idempotency-Key: webhook-test-$(uuidgen)" \
-d '{}'Likely errors: validation_failed, limit_exceeded, idempotency_key_reuse_mismatch, insufficient_scope.
