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 official TypeScript SDK in packages/steve-sdk includes matching helpers.
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 accepts only public HTTPS webhook targets. 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
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