Set up webhook signing

Enable signing, create a subscription, and configure your receiver to verify deliveries

To receive signed io.sign webhooks, enable signing on the webhooks service, create a subscription, and configure your receiver with that subscription's secret. Signing uses a hash-based message authentication code using SHA-256 (HMAC-SHA256).

Two secrets serve different purposes:

SecretWhere it belongsWhat it does
Deployment root keySecret manager for the io.sign webhooks serviceEnables signing and derives the secrets for every subscription in the deployment. The operator manages it.
Subscription secret (whsec_…)Secret manager for your webhook receiverVerifies deliveries for one subscription. Retrieve it through the API after signing is enabled.

Your receiver uses the subscription secret. Never give it the deployment root key, and do not generate the subscription secret yourself.

Before you start

You need:

  • The base URL of your io.sign deployment, with /v1/webhooks routed to its webhooks service.
  • A verified access token that proves your identity and membership of the subscription's organisation.
  • An HTTPS receiver endpoint where you can preserve the raw request body and verify signatures before processing events.
  • Access to your receiver's secret manager. For self-hosted io.sign, the operator also needs access to the webhooks service configuration.

Use a test environment for initial setup. Configure verification before triggering events that your application needs to process.

Step 1: Enable signing on the deployment

io.finnet-managed deployment

Ask your io.finnet contact to confirm that webhook signing is enabled for your deployment. io.finnet manages the deployment root key. Once signing is enabled, continue to Step 2.

Self-hosted deployment

Set the root key on the webhooks service, which runs separately from the signing API. Setting it only on the API or web console does not enable webhook signing.

  1. Generate a root key once. For example, this command produces a random 64-character hexadecimal string that meets the root key requirements:

    openssl rand -hex 32
  2. Store the generated value in your secret manager. Configure the webhooks service to receive it as WEBHOOKS_SIGNING_ROOT_KEY, and set WEBHOOKS_SIGNING_ENABLED=true to require a root key at startup. The resulting environment configuration is:

    WEBHOOKS_SIGNING_ROOT_KEY=<value-from-your-secret-manager>
    WEBHOOKS_SIGNING_ENABLED=true
  3. Restart or redeploy the webhooks service so it reads the new configuration. Use the same stored root key for every instance in the deployment and retain it across restarts.

The presence of WEBHOOKS_SIGNING_ROOT_KEY activates signing for every subscription in the deployment. WEBHOOKS_SIGNING_ENABLED=true makes startup fail if that key is missing; it cannot enable signing on its own. Leave WEBHOOKS_SIGNING_ROOT_KEY_PREVIOUS unset for initial setup. See Webhook signing configuration for all settings.

🚧

Coordinate changes to an existing deployment

Enabling signing adds signature headers to every delivery. Coordinate receiver configuration with your consumers. Keep the root key in your secret manager across deployments: replacing it changes every subscription secret and requires the root-key rotation procedure.

Step 2: Create or select a subscription

If you already have a subscription for your receiver, find its id with List webhook subscriptions and continue to Step 3. You do not need to recreate it to enable signing.

Otherwise, create a subscription. Set IO_SIGN_BASE_URL to your deployment's base URL and supply IO_SIGN_ACCESS_TOKEN through your secret manager or authenticated shell session. Replace the receiver URL in this example with your endpoint:

IO_SIGN_BASE_URL='https://sign.example.com'

curl --fail-with-body --request POST \
  "${IO_SIGN_BASE_URL}/v1/webhooks" \
  --header "Authorization: Bearer ${IO_SIGN_ACCESS_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"events":["signature.completed"],"url":"https://receiver.example.com/webhooks/io-sign"}'

Save the response's id as your subscription identifier. This example subscribes to completed signatures across your organisation. Add vaultId to restrict delivery to a vault, or choose other supported events. See Create a webhook subscription for the request fields.

There is no per-subscription signing switch or signing secret in the create request. The deployment configuration from Step 1 controls signing.

Step 3: Retrieve and store the subscription secret

Using the same base URL and access token, replace the identifier below with the subscription's id:

IO_SIGN_SUBSCRIPTION_ID='<subscription-id>'

curl --fail-with-body \
  "${IO_SIGN_BASE_URL}/v1/webhooks/${IO_SIGN_SUBSCRIPTION_ID}/secret" \
  --header "Authorization: Bearer ${IO_SIGN_ACCESS_TOKEN}"

Store the response's secret value, including its whsec_ prefix, in your receiver's secret manager. If the response includes a non-null previousSecret during a subscription rotation, store that too and accept both until previousExpiresAt. Do not put these values in source control or application logs.

A 409 response with meta.errorCode set to SIGNING_NOT_CONFIGURED means the deployment has no signing root key. Return to Step 1 and check the webhooks service configuration. A 403 PROVEN_PRINCIPAL_REQUIRED means the token did not prove the required identity and organisation. See Secret endpoint errors.

Step 4: Configure your receiver to require verification

Load the stored subscription secret into your receiver. Follow Verify webhook signatures for the Node.js example and Express raw-body configuration. Pass the accepted secrets as an array to verifyWebhook, together with the request headers and raw body buffer.

Set your receiver to require signatures for this subscription. Reject requests missing any of webhook-id, webhook-timestamp, or webhook-signature; never use missing headers as a reason to bypass verification. Check timestamp freshness, and deduplicate verified deliveries using webhook-id before processing events.

If one receiver handles several subscriptions, use your receiver configuration to select the appropriate subscription secret for each endpoint.

Step 5: Check a delivery

  1. Trigger an event covered by your subscription in your test environment. For the example above, complete a test signature request. If a suitable delivery already exists, use Redeliver a webhook to request another attempt.
  2. Confirm that your receiver receives webhook-id, webhook-timestamp, and webhook-signature, and that verification succeeds with the stored subscription secret.
  3. Check List webhook deliveries and List delivery attempts for the delivery result.
  4. Test that your receiver rejects a request with a changed body or missing signature header, and that sending the same verified delivery again does not process the event twice.

Setup is complete when a real delivery verifies successfully and your receiver rejects invalid requests and ignores duplicates. Keep the subscription secret available to your receiver across restarts, and follow Rotate a subscription secret when it needs to change.

See Delivery retries for attempt limits, retry delays, request timeouts, and manual redelivery.


Did this page help you?