Guide to OneSpan Webhooks: Features and Best Practices
OneSpan webhooks tell your systems how an e-signature transaction is progressing. When a OneSpan Sign package is created, sent, viewed, signed, declined, completed or expires, OneSpan can notify your application straight away, so you can update a deal, file the signed documents, chase a signer or start the next step of a workflow without polling the API.
This guide covers how OneSpan Sign callback notifications are registered and authenticated, the events you can subscribe to and what their payloads contain, the inconsistencies in OneSpan's own examples, and the practices that make a OneSpan receiver dependable.
What are OneSpan webhooks?
OneSpan calls its webhooks callback event notifications. They belong to OneSpan Sign, the e-signature product, and they report events on packages, which the newer interface calls transactions. Each account registers one callback URL, an optional callback key, and the list of events it wants. When one of those events happens, OneSpan Sign sends an HTTP POST with a JSON body describing it.
OneSpan does not sign callbacks. Instead, the callback key you register is sent back in the Authorization header as Basic {callbackKey}. Note what that is: the word Basic, a space, and your raw key. It is not a base64-encoded username:password pair, so HTTP Basic parsers will not read it correctly. Compare the whole header value instead.
OneSpan webhook features
| Feature | Details |
|---|---|
| Configuration | Admin > Event Notification, the Java or .NET SDK, or POST /api/callback |
| Scope | One callback URL and key per account, with the events you select |
| Method and format | POST, JSON body |
| Authentication | Authorization: Basic {callbackKey}, the raw key. The key is optional when registering |
| Signature and timestamp | None |
| Event field | name in the body |
| Common fields | @class, name, sessionUser, packageId, message, documentId, createdDate |
| Field limits | sessionUser and packageId up to 255 characters |
| Timeout, retries, ordering | Not documented |
| Source IPs | Not published. FedRAMP customers must have callback domains allowlisted by OneSpan Support |
Common events
OneSpan documents 26 events.
| Area | Events | Fires when |
|---|---|---|
| Package lifecycle | PACKAGE_CREATE, PACKAGE_ACTIVATE, PACKAGE_DEACTIVATE, PACKAGE_, PACKAGE_COMPLETE, PACKAGE_EXPIRE | A package is created, sent, moved back to draft, waits for the sender to complete it, completes or expires |
| Package housekeeping | PACKAGE_ARCHIVE, PACKAGE_TRASH, PACKAGE_RESTORE, PACKAGE_DELETE, TEMPLATE_CREATE | A package is archived, trashed, restored or deleted, or a template is created |
| Recipients | PACKAGE_DECLINE, PACKAGE_DISPUTE, PACKAGE_ATTACHMENT, ROLE_REASSIGN, COBROWSE_REQUEST | A recipient declines with a reason, disputes a specification, uploads an attachment, delegates to someone else, or asks the sender for a co-browsing session |
| Documents and signers | DOCUMENT_VIEWED, DOCUMENT_SIGNED, SIGNER_COMPLETE, SIGNER_LOCKED, KBA_FAILURE | A document is viewed or signed, a signer finishes all documents, a signer is locked out after repeated SMS or Q&A failures, or KBA authentication fails |
| Delivery and recordings | EMAIL_BOUNCE, RECORDINGS_READY | An email bounces, or a Virtual Room recording is ready to download |
| eDeposit | EDEPOSIT_SUCCESS, EDEPOSIT_FAILURE, EDEPOSIT_FAILURE_ATTEMPT | Documents are deposited to eOriginal, fail unrecoverably, or fail with more automated attempts to come |
Here is OneSpan's example of a completed package:
{
"@class": "com.silanis.esl.packages.event.ESLProcessEvent",
"name": "PACKAGE_COMPLETE",
"sessionUser": "e00696ec-d6f5-4feb-89c5-a5ce002a6c66",
"packageId": "5n4obeO8jYoPp126Cm-Y3fxdfbo=",
"message": null,
"documentId": null,
"createdDate": "2018-06-30T20:15:01.038Z"
}
packageId is the transaction. sessionUser is the sender's ID for sender-triggered events and the signer's ID for signer-triggered ones. On PACKAGE_COMPLETE with several signers, it is the last signer. It is null on system events such as eDeposit. message carries the bounce type on EMAIL_BOUNCE, the reason on PACKAGE_DECLINE and the attachment name on PACKAGE_ATTACHMENT. documentId is set on document events.
A few behaviours are worth knowing before you write a handler. DOCUMENT_SIGNED also fires when a signer accepts the consent or disclosure agreement, with documentId set to default-consent. PACKAGE_CREATE fires again if an API call puts a package that is already a draft into draft status. And after a ROLE_REASSIGN, later events identify the new signer by signer ID, so record it from sessionUser at that point.
Inspect OneSpan webhooks as they arrive. Register a Hookdeck Inspect URL as your callback URL in the sandbox to see each event's exact body and
Authorizationheader, with no account or setup required.
Setting up OneSpan webhooks
Generate a long random callback key first, for example with openssl rand -hex 32.
In the OneSpan Sign interface, go to Admin > Event Notification, enter your HTTPS callback URL and key, and turn on the events you need.
To register through the REST API, call POST /api/callback with your API key:
curl -X POST "https://sandbox.esignlive.com/api/callback" \
-H "Authorization: Basic $ONESPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/onespan",
"key": "your-long-random-callback-key",
"events": ["PACKAGE_CREATE", "PACKAGE_DECLINE", "PACKAGE_COMPLETE"]
}'
Use your production API URL instead of the sandbox for live accounts. With the Java SDK the same registration is eslClient.getEventNotificationService().register(newEventNotificationConfig(URL).withKey(KEY).forEvent(NotificationEvent.PACKAGE_. Afterwards, Admin > Event Notification shows the selected events toggled on.
Both url and key are optional in the API's request payload, so it is possible to register a callback with no key. Do not: without one, OneSpan sends nothing you can check.
FedRAMP customers have an extra step. For new or changed callback destinations, OneSpan Support must allowlist your callback domains before you configure them.
To route through Hookdeck, create a Hookdeck source with the OneSpan type and register its URL as the callback URL. The source has one field, Authorization Header: enter Basic followed by your callback key, exactly as OneSpan sends it. It accepts POST.
Securing OneSpan webhooks
Compare the whole Authorization header with Basic plus your callback key, in constant time. Do not base64-decode the value.
const crypto = require('crypto');
const express = require('express');
const CALLBACK_KEY = process.env.ONESPAN_CALLBACK_KEY;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/onespan', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('authorization');
// OneSpan sends the raw key after "Basic ", not base64 username:password.
if (!CALLBACK_KEY || typeof received !== 'string' || !safeEqual(received, `Basic ${CALLBACK_KEY}`)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
switch (event.name) {
case 'PACKAGE_COMPLETE':
// Download the signed documents for event.packageId.
break;
case 'PACKAGE_DECLINE':
// event.message holds the decline reason.
break;
default:
break;
}
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
CALLBACK_KEY = os.environ.get("ONESPAN_CALLBACK_KEY", "")
def verify_onespan_request(headers) -> bool:
if not CALLBACK_KEY:
return False # never accept when unconfigured
received = headers.get("authorization")
if not received:
return False
expected = f"Basic {CALLBACK_KEY}"
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
The callback key is a shared secret, not a signature. It proves the sender knows the key, but it does not cover the body, so it cannot detect a modified payload, and there is no timestamp, so a captured request can be replayed. Serve the callback URL over HTTPS only, use a long random key, and register a new one if it might have leaked.
Make OneSpan webhooks production-ready. Hookdeck Event Gateway checks the callback key in the
Authorizationheader, queues every transaction event durably, and retries delivery to your service on a schedule you control.
OneSpan webhook limitations and pain points
"Basic" that is not HTTP Basic
The Problem: A standard Basic auth middleware rejects every callback, or decodes the key into garbage.
Why It Happens: OneSpan sends Basic {callbackKey} with the raw key. HTTP Basic expects base64 username:password after Basic , so a library that parses it fails or returns nonsense.
Workarounds:
- Skip Basic auth middleware on this route and compare the whole header string.
- Keep the key free of characters that are awkward in headers, such as spaces and line breaks.
How Hookdeck Can Help: Hookdeck's OneSpan source compares the full Authorization value, Basic plus your key. Hookdeck answers OneSpan with a 200 either way, but a request with a missing or wrong key is recorded as failed verification and never reaches your service.
Delivery behaviour is undocumented
The Problem: You cannot tell how long OneSpan waits for a response, whether it retries a failed callback, or whether callbacks arrive in order.
Why It Happens: The callback documentation covers registration and payloads, not delivery.
Workarounds:
- Respond with a
2xxquickly and process afterwards. - Reconcile open packages against the OneSpan Sign API on a schedule, so a missed
PACKAGE_COMPLETEis caught.
How Hookdeck Can Help: Hookdeck accepts each callback and stores it, then delivers to your service with retries you configure. You can replay any callback after a fix, so delivery no longer depends on behaviour OneSpan does not document.
Examples that disagree with the event list
The Problem: A handler written from the event table misses some events.
Why It Happens: OneSpan's table lists EDEPOSIT_FAILURE_ATTEMPT, but the example payload in the same row has "name": "EDEPOSIT_. The eDeposit examples give createdDate as epoch milliseconds, such as 1732287648000, while the other events use ISO 8601 strings. PACKAGE_DISPUTE appears in only one of the page's three event tables.
Workarounds:
- Accept both spellings of the eDeposit retry event until you have seen one on the wire.
- Parse
createdDateas either a number or a string. - Log and acknowledge any
nameyou do not recognise.
How Hookdeck Can Help: Hookdeck stores every callback with its full body, so you can see the real name and date format your account sends, and filters can match either spelling.
Several events for one action
The Problem: One signer finishing a document can produce DOCUMENT_SIGNED for the consent agreement, DOCUMENT_SIGNED for each document, SIGNER_COMPLETE and finally PACKAGE_COMPLETE, all within seconds.
Why It Happens: Each step is its own event, and consent acceptance counts as signing a document.
Workarounds:
- Subscribe only to the events you act on.
PACKAGE_COMPLETEis usually the one that matters. - Ignore
DOCUMENT_SIGNEDevents whosedocumentIdisdefault-consentif you only track real documents.
How Hookdeck Can Help: Hookdeck filters can drop events you do not need, such as consent signatures, before they reach your service.
Best practices
Store the key, compare the header
Keep the callback key in your environment and build the expected header as Basic plus the key. Never compare a decoded value, and never log the header.
Key on the package
Use packageId to find the transaction in your system, and sessionUser to find the signer. Remember that IDs can contain =, - and _.
Deduplicate deliveries
Build an idempotency key from name, packageId, documentId and createdDate, and skip repeats. See our guide to webhook idempotency.
Fetch documents after completion, not during
On PACKAGE_COMPLETE, queue a job that downloads the signed documents from the API, rather than doing it in the request. See why to process webhooks asynchronously.
Test in the sandbox first
Register a sandbox callback, run a package through creation, signing and completion, and check every event your handler will see before you register the production URL.
Conclusion
OneSpan Sign sends callback notifications as unsigned JSON POST requests, with the callback key you registered in the Authorization header as Basic {callbackKey}. Always register a long random key, compare the whole header in constant time over HTTPS, route on name, allow for the inconsistent eDeposit examples, and reconcile against the API, because delivery behaviour is not documented.
Hookdeck Event Gateway checks the callback key, keeps every transaction event, and retries and replays delivery to your service, so a completed signature is never lost to an outage on your side.
Get started with Hookdeck for free and handle OneSpan webhooks reliably in minutes.