Webhooks
How SignCare notifies your server when asynchronous flows complete — what's sent, how to register URLs, and what to watch out for.
When webhooks are used
SignCare pushes webhook callbacks when an asynchronous flow reaches a terminal state. You don't poll — we POST to a URL you've registered. The flows that emit webhooks today:
- eSign — document signed, rejected, or expired
- Aadhaar OVSE — offline Aadhaar verification completed
- DigiLocker — issued document fetch completed
- NeSL eStamp — e-stamp workflow completed
- WealthScape — three separate streams covering consent lifecycle, financial data availability, and historical data fetch (see WealthScape webhooks)
Synchronous verifications (PAN, GST, Bank Account, IFSC, Voter ID, etc.) return the result in the HTTP response body and do not use webhooks.
Registering a webhook URL
Two mechanisms — a per-request URL, and an account-level default.
Per-request URL (eSign only)
For eSign you can pass responseUrl in the request body when creating a sign request. This takes precedence over the account-level default for that single transaction.
Account-level default (all flows)
Register a webhook URL per API type for your App ID. Use this when all your traffic should land at the same URL without passing it per request.
Supported apiType values: ESign, DigiLocker, NeSLEStamp, PhysicalEStamp.
You can retrieve the current configuration via GET /api/v1/user/webhook.
Resolution order
For each outbound webhook we resolve the URL as:
responseUrlin the original request body (eSign only)- Account-level
CompanyAppAccess.WebhookUrlfor that API type - If neither is set → the webhook is not sent (no error — the flow still completes, you can fetch the result via the polling endpoint)
What gets sent
All webhook deliveries are HTTP POST with Content-Type: application/json. No custom headers, no signature header.
Webhook payloads are currently unsigned. There is no HMAC signature or shared secret. If your endpoint is public, treat the payload as untrusted and validate it against your own records (match the documentId / txnId / transactionId to a request you actually made). Do not act on a webhook as authoritative identity proof without reconciling against a fetched record.
eSign payload
documentStatus is one of Completed, Rejected, Expired, Pending.
Aadhaar OVSE payload
On failure success is false, verification_passed is false, and data.message contains the reason.
DigiLocker payload
NeSL eStamp payload
WealthScape webhooks
WealthScape uses a different registration model from the other flows. Instead of PUT /api/v1/user/webhook or a per-request responseUrl, you provide three callback URLs to SignCare and we configure them on your account.
Share the three URLs with your SignCare onboarding contact:
| Stream | Purpose |
|---|---|
| Consent | Status changes across the consent lifecycle (Accepted, Rejected, Revoked, Expiring Soon, Expired) |
| Data | Financial information availability (raw + analysed) |
| Historical Data | Historical data fetch completion for eligible FI types |
All three deliveries are HTTP POST with Content-Type: application/json and follow the same retry / response semantics as the other flows below.
Consent webhook
Notifies you of changes in consent status. Sent once when the consent transitions to Accepted, Rejected, Revoked, Expiring Soon, or Expired.
Data webhook
Notifies you when financial information is available. The same flow emits two notifications: once when raw data is ready, and once when analysed data is ready. The dataType field distinguishes the two, and dataOrigin identifies the source (e.g. Account Aggregator, MFC).
dataType is RAW or ANALYSED. fiDataStatus indicates whether the data fetch succeeded; on failure, errMsg and errStage carry the diagnostic.
Historical Data webhook
Notifies you that historical data fetch has completed for an eligible FI type. Payload carries the consent/session identifiers, account, status, and the historical date range that was fetched.
status is one of HISTORICAL_DATA_FETCH_SUCCESS, HISTORICAL_DATA_FETCH_PARTIAL_SUCCESS, or HISTORICAL_DATA_FETCH_FAILED.
Responding to a webhook
Your endpoint must respond with any 2xx status code. SignCare does not inspect the response body — status code is the only signal.
Retry behavior
SignCare sends each webhook exactly once. There is currently no automatic retry. If your endpoint returns non-2xx, times out, or is unreachable, the webhook delivery is lost — no dead-letter, no second attempt.
Because of this, your architecture should:
- Not rely solely on the webhook. Use the polling endpoint (below) as a safety net for reconciliation.
- Keep your webhook endpoint highly available. Avoid synchronous work inside the handler; queue for processing and 2xx immediately.
- Monitor for missed events. Periodically poll pending transactions that should have completed by now.
Timeout: SignCare uses the default .NET HttpClient timeout (~100 seconds) when calling your URL. Keep your 2xx response under a few seconds to be safe.
Idempotency on your side
The same delivery should never arrive twice from SignCare today (since we don't retry), but you should still dedupe defensively using the transaction ID:
| Flow | Dedup key |
|---|---|
| eSign | documentId |
| Aadhaar OVSE | data.txn_id |
| DigiLocker | data.transactionId |
| NeSL eStamp | data.eStampId |
For WealthScape, dedup using the combination of identifiers that uniquely describe an event in your integration — e.g. consentId + status for Consent webhooks, txnId for Data webhooks, and consentId + accountId + fromDate + toDate for Historical Data webhooks. Confirm the exact contract with your SignCare onboarding contact before relying on a single field.
Store the ID in a table with a unique constraint and drop duplicate deliveries that might arrive if SignCare adds retries later.
The request_id field (OVSE and DigiLocker payloads only) echoes back whatever correlation ID you sent on the original request — useful for matching the webhook back to the call site in your logs.
Polling — the recommended complement
Most flows expose a polling endpoint. Use it for reconciliation, for first-time integration testing, and as a fallback when webhooks are undeliverable.
| Flow | Polling endpoint |
|---|---|
| eSign | POST /api/v1/esign/status with documentId in body |
| Aadhaar OVSE | GET /api/v1/aadhaar-ovse/result/{txnId} |
| DigiLocker | GET /api/v1/digilocker/details/{transactionId} |
| NeSL eStamp | Webhook only — no polling endpoint currently |
| WealthScape | See the WealthScape API reference for fetch endpoints per FI type |
Polling endpoints return the same data shape as the webhook payload (or a pending: true stub if the flow hasn't completed yet).
Source IPs
Webhook traffic originates from SignCare's hosting infrastructure. We do not publish a stable list of source IPs today — allowlist by hostname if your firewall supports it, or contact support for a current snapshot.
Testing webhooks locally
During integration against Stage (https://uat-ext.signcare.io):
- Expose your local handler publicly with ngrok, localtunnel, or Cloudflare Tunnel.
- Register the tunnel URL either in the request body (eSign
responseUrl) or viaPUT /api/v1/user/webhook. - Trigger the flow from your app against Stage — your tunnel receives the POST.
- Use webhook.site if you only want to inspect the payload shape without wiring a real handler.
What we're adding later
These are on the roadmap — not live yet. Do not build against them:
- HMAC-SHA256 signature header for payload verification
- Automatic retry with exponential backoff
- Dead-letter storage for failed deliveries
- Per-delivery event ID for strict dedup
- A webhooks dashboard in the portal for delivery history + manual replay
When any of these ship, this page will be updated and we'll email your registered technical contact.
See also
- Environments — Stage vs Live URLs
- Idempotency — request-side correlation IDs
- Error Codes — for responses on the polling endpoints