Gareth Wilson Gareth Wilson

Guide to OneSpan Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationAdmin > Event Notification, the Java or .NET SDK, or POST /api/callback
ScopeOne callback URL and key per account, with the events you select
Method and formatPOST, JSON body
AuthenticationAuthorization: Basic {callbackKey}, the raw key. The key is optional when registering
Signature and timestampNone
Event fieldname in the body
Common fields@class, name, sessionUser, packageId, message, documentId, createdDate
Field limitssessionUser and packageId up to 255 characters
Timeout, retries, orderingNot documented
Source IPsNot published. FedRAMP customers must have callback domains allowlisted by OneSpan Support

Common events

OneSpan documents 26 events.

AreaEventsFires when
Package lifecyclePACKAGE_CREATE, PACKAGE_ACTIVATE, PACKAGE_DEACTIVATE, PACKAGE_READY_FOR_COMPLETE, PACKAGE_COMPLETE, PACKAGE_EXPIREA package is created, sent, moved back to draft, waits for the sender to complete it, completes or expires
Package housekeepingPACKAGE_ARCHIVE, PACKAGE_TRASH, PACKAGE_RESTORE, PACKAGE_DELETE, TEMPLATE_CREATEA package is archived, trashed, restored or deleted, or a template is created
RecipientsPACKAGE_DECLINE, PACKAGE_DISPUTE, PACKAGE_ATTACHMENT, ROLE_REASSIGN, COBROWSE_REQUESTA 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 signersDOCUMENT_VIEWED, DOCUMENT_SIGNED, SIGNER_COMPLETE, SIGNER_LOCKED, KBA_FAILUREA 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 recordingsEMAIL_BOUNCE, RECORDINGS_READYAn email bounces, or a Virtual Room recording is ready to download
eDepositEDEPOSIT_SUCCESS, EDEPOSIT_FAILURE, EDEPOSIT_FAILURE_ATTEMPTDocuments 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 Authorization header, 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_COMPLETE)). 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 Authorization header, 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 2xx quickly and process afterwards.
  • Reconcile open packages against the OneSpan Sign API on a schedule, so a missed PACKAGE_COMPLETE is 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_ATTEMPT_FAILURE". 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 createdDate as either a number or a string.
  • Log and acknowledge any name you 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_COMPLETE is usually the one that matters.
  • Ignore DOCUMENT_SIGNED events whose documentId is default-consent if 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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.