Skip to content
Hightop docs header art
Hightop
API and Integrations

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#

curlrequest
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:

textexample
${timestamp}.${raw_body}

Use the raw request body, not parsed JSON.

typescriptexample
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#

curlrequest
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.

Previous

Withdraw to Crypto

Next

Connect with MCP