Docs

Webhook Endpoints

Register, manage, and debug the HTTPS endpoints that receive Stile's signed event notifications.

A webhook endpoint is an HTTPS URL on your server where Stile delivers signed events — session verified, failed, expired, and more. This page covers the management API: creating endpoints, rotating secrets, and inspecting individual deliveries. For signature verification and handler patterns, see the Webhooks guide. You can also manage endpoints in the dashboard.

Examples show cURL, Python, Go, and Node.js. Approved preview users can also use the private-preview Node.js SDK as a typed convenience wrapper.

Live orgs require a webhook endpoint

You cannot create verification sessions until at least one active webhook endpoint exists (the API returns 400 webhook_required). Sandbox organizations are exempt.

The webhook endpoint object

FieldTypeDescription
idstringUnique identifier, prefixed we_.
application_idstring | nullOwning application, or null for a legacy organization-wide endpoint.
namestringOptional display name.
urlstringThe HTTPS URL events are delivered to.
enabled_eventsstring[]Subscribed event types. ["*"] means all events.
statusstringenabled or disabled. Disabled endpoints receive no deliveries.
descriptionstringOptional operational notes.
metadataobjectKey-value string pairs you attach.
secretstringThe signing secret used to compute Stile-Signature. Returned once — on create and on rotate only.

Create an endpoint

POST/v1/webhook_endpoints

Your endpoint should be a publicly reachable HTTPS URL and must respond with a 2xx within 30 seconds. Use an HTTPS tunnel for a local handler. Return a 2xx as soon as you've queued the work, then process asynchronously.

ParameterTypeDescription
namestringOptional display name, up to 100 characters.
urlrequiredstringThe HTTPS URL to deliver events to.
enabled_eventsrequiredstring[]Event types to subscribe to. Use ["*"] to receive all events.
descriptionstringOperational notes for this endpoint.
metadataobjectKey-value string pairs to attach to the endpoint.
curl https://api.stile.id/v1/webhook_endpoints \
  -H "Authorization: Bearer stile_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/api/webhooks",
    "enabled_events": ["verification_session.verified", "verification_session.failed"]
  }'
import requests

res = requests.post(
    "https://api.stile.id/v1/webhook_endpoints",
    headers={"Authorization": "Bearer stile_sk_..."},
    json={
        "name": "Production verification",
        "url": "https://yourapp.com/api/webhooks",
        "enabled_events": [
            "verification_session.verified",
            "verification_session.failed",
        ],
        "description": "Production webhook",
    },
)
endpoint = res.json()

# IMPORTANT: save endpoint["secret"] — it's only shown once!
print("Webhook secret:", endpoint["secret"])
body := strings.NewReader(`{
  "name": "Production verification",
  "url": "https://yourapp.com/api/webhooks",
  "enabled_events": ["verification_session.verified", "verification_session.failed"],
  "description": "Production webhook"
}`)
req, _ := http.NewRequest("POST", "https://api.stile.id/v1/webhook_endpoints", body)
req.Header.Set("Authorization", "Bearer stile_sk_...")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
const endpoint = await stile.webhookEndpoints.create({
  name: "Production verification",
  url: "https://yourapp.com/api/webhooks",
  enabled_events: [
    "verification_session.verified",
    "verification_session.failed",
    "verification_session.expired",
  ],
  description: "Production webhook",
});

// IMPORTANT: save endpoint.secret — it's only shown once!
console.log("Webhook secret:", endpoint.secret);

Store the secret immediately

The response includes the endpoint's signing secret exactly once. Store it in your secret manager right away — you need it to verify the Stile-Signature header on every delivery. If you lose it, rotate the secret to get a new one.

Retrieve an endpoint

GET/v1/webhook_endpoints/:id
curl https://api.stile.id/v1/webhook_endpoints/we_abc123 \
  --header "Authorization: Bearer $STILE_API_KEY"

The secret is not included — it is only returned on create and rotate.

Update an endpoint

POST/v1/webhook_endpoints/:id
ParameterTypeDescription
namestring | nullRename or clear the display name.
urlstringNew delivery URL.
enabled_eventsstring[]Replace the subscribed event types.
status"enabled" | "disabled"Pause or resume delivery without deleting the endpoint.
descriptionstring | nullUpdate or clear operational notes.
metadataobjectReplace all metadata key-value pairs.
curl https://api.stile.id/v1/webhook_endpoints/we_abc123 \
  --request POST \
  --header "Authorization: Bearer $STILE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "enabled_events": ["*"], "status": "disabled" }'

Delete an endpoint

DELETE/v1/webhook_endpoints/:id
curl https://api.stile.id/v1/webhook_endpoints/we_abc123 \
  --request DELETE \
  --header "Authorization: Bearer $STILE_API_KEY"

List endpoints

GET/v1/webhook_endpoints
curl https://api.stile.id/v1/webhook_endpoints \
  -H "Authorization: Bearer stile_sk_..."
import requests

res = requests.get(
    "https://api.stile.id/v1/webhook_endpoints",
    headers={"Authorization": "Bearer stile_sk_..."},
)
data = res.json()
req, _ := http.NewRequest("GET", "https://api.stile.id/v1/webhook_endpoints", nil)
req.Header.Set("Authorization", "Bearer stile_sk_...")
res, _ := http.DefaultClient.Do(req)
const { data } = await stile.webhookEndpoints.list();

Rotate the signing secret

POST/v1/webhook_endpoints/:id/rotate-secret

Generates a new signing secret for the endpoint and returns it once.

curl -X POST https://api.stile.id/v1/webhook_endpoints/we_abc123/rotate-secret \
  -H "Authorization: Bearer stile_sk_..."

Rotation takes effect immediately

Update your environment variables as soon as you rotate — deliveries signed with the old secret will stop validating.

List deliveries

GET/v1/webhook_endpoints/:id/deliveries

Returns a paginated list of delivery records for a specific endpoint, newest first. Stile creates one record for each event-endpoint pair and updates that record on every retry. Each record contains the current attempt number and latest HTTP response; it is not an immutable attempt history. The record ID is also sent as the Stile-Webhook-Id header.

ParameterTypeDescription
limitnumber= 10Number of deliveries to return.
starting_afterstringReturn older deliveries that follow this ID in the newest-first list.
ending_beforestringReturn newer deliveries that precede this ID in the newest-first list.
statusstringFilter by "delivered", "failed", or "pending".
event_typestringFilter by an exact event type.

Use only one cursor per request: starting_after moves toward older deliveries and ending_before moves toward newer deliveries.

curl "https://api.stile.id/v1/webhook_endpoints/we_abc123/deliveries?limit=10" \
  --header "Authorization: Bearer $STILE_API_KEY"

Retrieve a delivery

GET/v1/webhook_endpoints/:id/deliveries/:deliveryId

Returns the current detail for one delivery record, including its request payload, current attempt number, and latest response from your endpoint. Earlier retry responses are not retained on this resource.

curl https://api.stile.id/v1/webhook_endpoints/we_abc123/deliveries/cm5x7k9h20000qwerty123456 \
  --header "Authorization: Bearer $STILE_API_KEY"

Retry a delivery

POST/v1/webhook_endpoints/:id/deliveries/:deliveryId/retry

Re-queues an existing delivery immediately. The same delivery record and Stile-Webhook-Id are reused, and its attempt counter and latest response fields are reset before processing resumes. Use this after fixing an endpoint instead of waiting for the automatic retry schedule (5 min → 30 min → 2 h → 8 h after the initial failure; see the Webhooks guide).

curl -X POST https://api.stile.id/v1/webhook_endpoints/we_abc123/deliveries/cm5x7k9h20000qwerty123456/retry \
  -H "Authorization: Bearer stile_sk_..."

Send a test event

POST/v1/webhook_endpoints/:id/test

Queue a synthetic event to confirm that the endpoint is reachable and your handler accepts the payload. Omit event_type to send verification_session.verified.

curl https://api.stile.id/v1/webhook_endpoints/we_abc123/test \
  --request POST \
  --header "Authorization: Bearer $STILE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "event_type": "verification_session.verified" }'

The response contains both the queued event id and its endpoint-specific delivery_id. Retrieve that delivery until it reaches delivered or failed; accepting the test request does not itself prove that your endpoint returned 2xx.

{
  "id": "evt_test_123",
  "object": "event",
  "type": "verification_session.verified",
  "created": 1786400000,
  "delivery_id": "whd_test_123"
}

Automatic and manual retries reuse the original event id and delivery ID. Another subscribed endpoint has its own delivery record for the same event, so deduplicate business work on the event id.

Event types

See the full catalog in the Events API reference — verification session lifecycle (verification_session.*), manual review outcomes (session_review.*), and trust-reuse grants (trust_reuse_grant.*, trust_reuse_consent.*). Use ["*"] in enabled_events to subscribe to everything.

Next steps

On this page