Receiving webhook events
Create and configure the destination first, as described in Webhook subscriptions. This guide covers the requests that Uptivo sends to that destination.
Receive events
Section titled “Receive events”Uptivo sends a POST request whose Content-Type is
application/json; charset=utf-8. The body is always a JSON array, even when it
contains one event. Current subscriptions request one event per delivery, but
your handler should iterate the array so it remains compatible with batching.
[ { "id": "example-event-id", "topic": "club-100001-events", "subject": "Athletes/300001", "data": {}, "eventType": "Athlete.Updated", "eventTime": "2026-09-07T10:30:00Z", "dataVersion": "1.0", "metadataVersion": null }]Use eventType to choose the schema for data. Consumers must tolerate
additional properties and safely ignore event types they do not yet support.
Never append subject blindly to the Club API base URL; see the
event catalog for each field’s semantics.
Minimal TypeScript handler
Section titled “Minimal TypeScript handler”The following framework-neutral handler uses the standard web Request and
Response interfaces. Replace persistForLater with a durable database or
queue write. Do not perform the complete business workflow before responding.
type UptivoEvent = { id: string; topic: string; subject: string; eventType: string; eventTime: string; dataVersion: string; data: Record<string, unknown>;};
const validationEventType = 'Microsoft.EventGrid.SubscriptionValidationEvent';
async function persistForLater(event: UptivoEvent): Promise<void> { // Insert into a durable queue or inbox table. // Enforce a unique constraint on event.id to make retries idempotent.}
export async function handleUptivoWebhook(request: Request): Promise<Response> { let events: UptivoEvent[];
try { const body: unknown = await request.json(); if (!Array.isArray(body)) { return new Response('Expected a JSON array', { status: 400 }); } events = body as UptivoEvent[]; } catch { return new Response('Invalid JSON', { status: 400 }); }
const validationEvent = events.find( (event) => event.eventType === validationEventType, );
if (validationEvent) { const validationCode = validationEvent.data.validationCode; if (typeof validationCode !== 'string') { return new Response('Missing validation code', { status: 400 }); }
return Response.json({ validationResponse: validationCode }); }
try { for (const event of events) { await persistForLater(event); } } catch { // A retryable status asks Uptivo to deliver the event again. return new Response('Unable to persist events', { status: 500 }); }
return new Response(null, { status: 204 });}This example uses 400 only for permanently malformed input. A temporary queue
or database failure returns 500, which allows delivery to be retried.
Endpoint validation
Section titled “Endpoint validation”Synchronous validation handshake
Section titled “Synchronous validation handshake”When a subscription is created, Uptivo first sends a
Microsoft.EventGrid.SubscriptionValidationEvent. Its data contains a
validationCode. Return it unchanged in a JSON object named
validationResponse:
{ "validationResponse": "<the received validationCode>"}Return that response synchronously; do not enqueue the validation event and do not construct the code yourself. The minimal handler above implements this path. No business events are delivered until the endpoint has been validated.
Asynchronous validation
Section titled “Asynchronous validation”If your endpoint cannot return the validation code synchronously, read the
validationUrl from the validation event’s data and perform a GET request
to that exact URL. The URL is valid for five minutes. During that interval,
ProvisioningState is AwaitingManualAction; if it expires, the state becomes
Failed and you must create the subscription again.
The values in the validation event example are intentionally fictitious. Never derive a validation URL from those values.
Delivery guarantees and retries
Section titled “Delivery guarantees and retries”Delivery is at least once: the same event can arrive more than once, and
events are not guaranteed to arrive in order. Use id as a deduplication key,
make downstream changes idempotent, and use eventTime or the current API state
when ordering matters.
Persist the complete event before acknowledging it. Acknowledge quickly and process it asynchronously; waiting for downstream APIs increases duplicates and can make the endpoint unhealthy.
Uptivo considers these responses successful: 200, 201, 202, 203, and
204. It does not retry 400, 401, 403, 404, or 413. Use those failure
statuses only when repeating the same delivery cannot succeed. Other failures
are retried.
After delivery, Uptivo waits up to 30 seconds for a response. A missing or retryable response queues the event for exponential-backoff retries for up to 24 hours. The best-effort schedule is 10 seconds, 30 seconds, 1 minute, 5 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, and then every 12 hours. Small random delays can be added, and some retries can be skipped when an endpoint remains unhealthy. A late successful response may not prevent a duplicate already queued for delivery.
Repeated failures also trigger delayed delivery: new events and retries to an unhealthy endpoint can be held back, sometimes for several hours, to avoid overloading it.
Security and test endpoints
Section titled “Security and test endpoints”The current public webhook subscription contract configures only Url and
EventTypes; it does not expose a signing secret, signature header, or custom
authorization header for subsequent deliveries. The validation handshake proves
that you control the endpoint at creation time, but it must not be treated as a
signature on later requests. Do not assume an undocumented header authenticates
an event. Apply normal endpoint protections, validate the JSON shape, accept
only expected event types, and reconcile sensitive state through the
authenticated Club API.
Temporary request-inspection services and public tunnels are useful during development, but webhook payloads can contain personal or club data. Use only accounts and environments approved for that data, avoid production events, and delete test subscriptions when finished.