Skip to content

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.

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.

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.

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.

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

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.