Skip to main content
Webhooks send HTTP POST requests to your server when events occur in Grantiva. Available on Pro, Business, and Enterprise plans.

Endpoint limits

Creating an endpoint on the Free plan returns a 403.

Events

Device and attestation events fire on paid plans only.
The high-risk threshold is your organization’s compliance maxRiskScore, defaulting to 50 when unset. The event payload carries the threshold that was applied.

Setup

Create webhook endpoints from Webhooks in the dashboard, or with POST /api/v1/org/webhooks.
  1. Enter your endpoint URL — use HTTPS, since the signing secret only protects integrity, not confidentiality
  2. Select which events to subscribe to (at least one)
  3. Save — Grantiva generates a signing secret (whsec_...) and returns it once, at creation
An endpoint receives an event only if that event’s name is in its subscription list and the endpoint is active.

Payload format

Every delivery is a JSON object with three top-level keys:
Every value inside data is a string. Numbers and booleans are serialized as "82" and "true", not 82 and true. Parse them on your side — data.risk_score > 75 is a string comparison and will not do what you expect.
timestamp is ISO-8601 UTC. The body is serialized with sorted keys, so the bytes you receive are the bytes that were signed.

Fields per event

key_id is the App Attest key id — the stable per-device identifier, and the same value as the sub claim in the attestation JWT. device_id is the optional device id supplied on the challenge request; the iOS SDK does not send it, so it is usually an empty string. Key your own records on key_id. device_model and os_version fall back to the literal string "unknown" when the SDK did not report them.

Request headers

Verifying signatures

The signature is an HMAC-SHA256 of the raw request body keyed with your endpoint’s secret, hex-encoded and prefixed with sha256=.
Always verify signatures before processing webhook payloads, and always sign the bytes you received — re-serializing the parsed JSON can change key order or spacing and break the comparison.

Subscription events

subscription.changed fires whenever a subscription entitlement materially changes — tier, status, interval, expiry, auto-renew, or product. Identical redeliveries from Apple or Stripe do not re-fire it. subscription.expired additionally fires when status becomes expired or revoked, and subscription.refunded on refunds. All three carry the same data.
Handling notes:
  • Dedupe on data.event_id and key any cache eviction by data.subject_id.
  • expires_at is ISO-8601 here but epoch seconds in the JWT claim — each surface follows its own convention. It is an empty string when the source reports no expiry.
  • Treat the webhook as a cache-bust nudge: re-read the claim from the next JWT rather than trusting the webhook body as authoritative.

Delivery, retries, and history

A delivery counts as successful on any 2xx response. Anything else — a non-2xx status or a connection error — is a failure. Grantiva makes at most 3 delivery attempts: After the third failure the delivery is marked permanently failed. Retries are picked up by a background sweep, so the delays are lower bounds rather than exact times. Grantiva records the HTTP status, the first 1000 characters of your response body, and the error for every attempt. Read the history from the dashboard or GET /api/v1/org/webhooks/:id/deliveries, and re-drive a failed one with POST /api/v1/org/webhooks/:id/deliveries/:deliveryId/retry — a manual retry resets the attempt counter and gets a fresh budget of 3. Delivery is fire-and-forget: a webhook that fails never fails the attestation or dashboard action that triggered it.

Testing

Send a test event from the webhook detail page in the dashboard, or with POST /api/v1/org/webhooks/:id/test. This synchronously delivers a synthetic attestation.completed payload — signed like a real event — and reports the HTTP status, response body, and latency your endpoint returned, so you can verify reachability and signature handling.
attestation.completed is a test-only event name. You cannot subscribe to it, and it is never delivered outside a test. The test delivery is not recorded in delivery history and is not retried.