Webhooks
Delivery contract, signature verification, retries, and testing for Steve webhooks.
Steve uses webhooks for asynchronous session and submission updates.
There are two related pieces:
POST /api/v1/webhooks/testfor reachability checks- production deliveries for sessions created with a
webhookUrl
Connectivity test
Use the test endpoint before turning on production deliveries:
Steve performs a direct POST with this payload:
Test-delivery behavior:
- timeout: 5 seconds
- content type:
application/json - user agent:
steve-webhook/1.0 - signature header: not present on test requests
Production delivery headers
Production deliveries include:
| Header | Meaning |
|---|---|
Content-Type: application/json | JSON payload |
X-Webhook-Signature | HMAC-SHA256 signature |
X-Webhook-Event | Event type string |
X-Webhook-Id | Stable delivery id across retries |
X-Webhook-Timestamp | Unix seconds when the attempt was sent |
User-Agent: steve-webhook/1.0 | Sender identifier |
Production payload shape
Steve sends one generic envelope for both session-completion and later submission-action events:
How to read the key fields:
eventIdis the stable event identifier that also appears inX-Webhook-IdeventTypeis the primary routing fieldoccurredAtis the business timestamp for the event itselfstateis the session lifecycle statesubmissionStatusis the public business outcome when Steve can determine itresultis the workflow-specific processing payloadclientSubmissionIdandmetadataare echoed from session creation
Event types
Steve currently emits these event types:
| Event type | Fired when |
|---|---|
session.completed | Processing finished and no review is required |
session.failed | Processing failed |
session.review_required | Processing finished and a reviewer must decide the outcome |
submission.approved | A submission was approved |
submission.rejected | A submission was rejected |
submission.cancelled | A submission was cancelled |
submission.fraud_match_resolved | A fraud match was resolved |
Two important nuances:
- Session lifecycle routing belongs to
eventType, not to a separate internal status field. - Use
submissionStatusto distinguishreviewfromapproved,synced,rejected,cancelled, orfailed. - submission action events reuse the same envelope and stay intentionally small. If you need the latest reasons, fraud state, or file links, refetch
GET /api/v1/submissions/{submissionId}.
Signature verification
Production deliveries include:
Steve signs the raw request body using HMAC-SHA256 with this secret:
That means your receiver must first hash the API key to a hex string and then use that hex string as the HMAC key. The private TypeScript SDK exports deriveWebhookSecret, signWebhookPayload, and verifyWebhookSignature. When the SDK is available, pass deriveWebhookSecret(apiKey)'s result as the secret argument to verifyWebhookSignature.
Deduplication and replay protection
Use the headers together:
- store
X-Webhook-Idto deduplicate retries - reject stale deliveries using
X-Webhook-Timestamp
Typical replay window:
Retry behavior
Steve gives each delivery attempt 10 seconds to complete. If the target does not return 2xx in time:
- the initial attempt fails
- retry 1 is scheduled 30 seconds later
- retry 2 is scheduled 5 minutes later
- retry 3 is scheduled 30 minutes later
- after the third retry fails, delivery status becomes
failed
Make your handler idempotent and return quickly.
Webhook URL validation
Steve applies syntactic validation to require HTTPS webhook targets and reject known local destinations. It rejects URLs that:
- are not valid absolute URLs
- do not use
https:// - target
localhostor127.0.0.1 - target RFC1918 private ranges
- end in
.internalor.local
Steve does not resolve DNS during validation. A public-looking hostname can therefore pass this check even if it later resolves to a private address.
Recommended receiver pattern
- verify
X-Webhook-Signature - validate
X-Webhook-Timestamp - deduplicate on
X-Webhook-Id - route on
eventType - use
stateandsubmissionStatusfor lightweight routing, then refetch the submission or job if you need the latest full state - return
200as soon as the event is durably recorded