Webhook Setup
Send signed events to VertexY so the platform can build velocity, graph, and behavioural history.
What /events/ingest is for
POST /events/ingest is how you stream your event history into VertexY.
Use it for:
- user creation and login activity
- order lifecycle changes
- payment success and failure
- refunds, disputes, and chargebacks
Assessment answers the question: “Should I approve this transaction right now?”
Event ingestion answers the question: “What history and relationships should VertexY learn from over time?”
Authentication model
This endpoint does not use bearer auth.
Instead, every request must include:
x-event-signaturex-event-timestampx-event-nonce
The signature is:
HMAC-SHA256(rawRequestBody, webhookSigningSecret)Minimal event example
{
"companyId": "a1b2c3d4-e5f6-4789-abcd-ef1234567890",
"eventSource": "checkout-service",
"externalEventId": "evt_100001",
"idempotencyKey": "evt_100001",
"userId": "user_123",
"eventType": "payment_succeeded",
"timestamp": "2026-04-07T12:00:00.000Z",
"metadata": {
"email": "buyer@example.com",
"ipAddress": "203.0.113.10",
"deviceId": "device_abc_001",
"orderId": "order_100001"
},
"paymentDetails": {
"methodType": "card",
"gatewayPaymentId": "pay_100001",
"fingerprint": "pm_hash_001",
"amountMinor": 4999,
"currency": "USD",
"authStatus": "authorized"
}
}Working example with Node.js
import crypto from 'crypto';
const event = {
companyId: process.env.VERTEXY_COMPANY_ID,
eventSource: 'checkout-service',
externalEventId: 'evt_100001',
idempotencyKey: 'evt_100001',
userId: 'user_123',
eventType: 'payment_succeeded',
timestamp: new Date().toISOString(),
metadata: {
email: 'buyer@example.com',
ipAddress: '203.0.113.10',
deviceId: 'device_abc_001',
orderId: 'order_100001',
},
paymentDetails: {
methodType: 'card',
gatewayPaymentId: 'pay_100001',
fingerprint: 'pm_hash_001',
amountMinor: 4999,
currency: 'USD',
authStatus: 'authorized',
},
};
const body = JSON.stringify(event);
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomUUID();
const signature = crypto
.createHmac('sha256', process.env.VERTEXY_WEBHOOK_SECRET)
.update(body)
.digest('hex');
const res = await fetch('https://api.vertexY.com/api/events/ingest', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-event-signature': signature,
'x-event-timestamp': timestamp,
'x-event-nonce': nonce,
},
body,
});
const data = await res.json();Response shape
{
"id": "be0a9801-8d40-412e-a5df-5c70a3a42f55",
"status": "accepted",
"duplicate": false,
"assessment": {
"assessmentId": "c8acf6d5-8bf4-4b80-a7e5-1b33583c1c23",
"riskScore": 31,
"action": "review",
"recommendedAction": "review",
"policyMode": "hybrid",
"riskLevel": "medium",
"reasonCodes": [],
"featureContributions": {},
"engineVersion": "v2",
"latencyMs": 43
}
}Idempotency and retries
idempotencyKey is required.
Use it to make retries safe:
- first delivery creates the event
- repeat delivery with the same key is treated as a duplicate
- duplicates do not increment usage again
In rare races, a duplicate request may receive:
503 Service Unavailable- code:
ASSESSMENT_PENDING
That means the original event was accepted but its canonical assessment is still being finalized. Retry shortly.
Replay protection
The ingest pipeline rejects:
- missing timestamp or nonce
- stale timestamps outside the allowed replay window
- repeated nonce values within the configured nonce TTL
Rate limiting
The public ingest route is rate limited. If you exceed the window you receive 429 Too Many Requests.
Recommended event strategy
Send all supported event types, not just successful payments.
At minimum, ingest:
user_createdlogin_eventorder_createdpayment_failedpayment_succeededdispute_openedchargeback_received
Tips for higher-quality models
- Keep
userIdstable across your systems. - Use gateway or provider event IDs as
externalEventIdwhen possible. - Use the same identity signals in both assess and ingest.
- Include
paymentDetailsfor payment events. - Include contextual fields like
billingAddress,shippingAddress,deviceMeta, andipGeo.