Webhooks

Receive real-time event notifications the moment something changes in VaultN — orders, inventory, prices, promotions, and more — delivered straight to your systems via signed HTTPS POST.

Guide

Navigate this guide using the menu above. §1–3 are for everyone. §4–6 are for developers. §7 is the event catalogue — the automation ideas live there.


1. How webhooks work, end to end

Webhooks are how VaultN tells your systems that something has changed — a new order, an inventory movement, a price update, a connection change — without you having to poll our APIs. When an event happens, VaultN sends an HTTPS POST to a URL you control, with a signed JSON payload. You react. That reaction is your automation.

End-to-end webhook flow diagram

Things to know up front

  • 🔁 At-least-once delivery — The same event may arrive more than once. Your handler must be idempotent — see §5.
  • 15-second timeout — Reply quickly; do work asynchronously. VaultN times out after 15 seconds and treats it as a transient failure.
  • 🔄 Automatic retries — Exponential backoff for ~26 hours, up to 10 attempts. After that, reconcile state via our APIs.
  • 🔒 Signed payloads — Every request carries an X-VaultN-Signature HMAC. Verify it before trusting the body.

Not strictly ordered. Two events about the same entity can arrive out of order. Use timestamps inside the payload, not arrival order. Endpoints must be https:// — no localhost or self-signed certs in production.


2. Setting up a webhook (UI)

You manage webhooks from the VaultN Portal → Account & Help → Developer → Webhooks section.

Create a subscription

1. Click New subscription

2. Fill in the details

  • Endpoint URL — the public HTTPS URL where VaultN should send events. Get this from your dev team. For local development, use a tunnel like ngrok or webhook.site.
  • Event types — pick one or more from the catalogue (§7). You can change this later.
  • Custom secret (optional) — leave blank to let VaultN generate one. Only set this if you have a specific reason, e.g. centralized secret rotation.
  • Active — leave on. Toggle off to pause deliveries without losing the subscription.

3. Click Save

4. Copy the secret immediately

It's shown once. Hand it to your dev team via your secret manager. If you lose it, you'll need to rotate — see §8.

⚠️

Important: The webhook secret is only shown once at creation. Store it safely and immediately. If it's lost, you must rotate to get a new one.


3. What VaultN sends

Each delivery is a standard POST request with Content-Type: application/json; charset=utf-8. The body follows the CloudEvents 1.0 envelope shape.

Headers

HeaderExamplePurpose
X-VaultN-Signaturesha256=5e3f8a91…HMAC-SHA256 of the raw body, lowercase hex. Always verify.
X-VaultN-Delivery-Id8e7c23d0-ade9-…Unique per HTTP attempt (changes between retries). Useful for support.
User-AgentVaultN-Webhooks/1.0Allowlist this in your WAF if needed.
X-VaultN-Instance-Idwebhook-delivery-7c4b9fSender instance — quote it when raising support tickets.

Payload envelope

Every event body has the same outer shape. Only the data block varies.

{
  "id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "source": "vaultn.platform",
  "specversion": "1.0",
  "type": "vaultn.order.status.changed",
  "subject": "orders/f6a7b8c9-d0e1-2345-fabc-456789012345",
  "time": "2026-04-27T10:32:18Z",
  "datacontenttype": "application/json",
  "data": { "...event-specific fields..." }
}
FieldNotes
idStable event ID. Same value across all retries of the same event. Use this as your idempotency key.
sourceAlways vaultn.platform.
specversionCloudEvents version. Today: 1.0.
typeOne of the catalogue keys in §7. Switch on this in your handler.
subjectStructured {resourceType}/{guid} path (e.g. orders/{orderGuid}, products/{productGuid}). Useful for logs and routing.
timeWhen the event occurred, UTC, ISO 8601.
datacontenttypeAlways application/json.
dataEvent-specific payload — see §7. All field names are camelCase, all timestamps UTC.

4. Building a receiver

Endpoint requirements

  • Public HTTPS URL with a valid TLS certificate (no self-signed).
  • Accepts POST with application/json. Read the body as raw bytes before parsing — you need the raw bytes for signature verification.
  • Responds within 15 seconds. VaultN times out after that and treats it as a transient failure.
  • Returns 2xx on success. Anything else is treated as a failure — see §5.

The standard pattern: verify → enqueue → 200. Never run heavy business logic inside the request handler. Drop the verified event onto a queue (SQS, RabbitMQ, Postgres outbox) and process it from a worker. This keeps your endpoint fast and resilient under bursts.

Receiver pattern: verify, enqueue, respond 200, worker processes

Verifying the signature

VaultN signs the raw request body with HMAC-SHA256 using your subscription's secret. The hex digest is sent as X-VaultN-Signature: sha256=<hex>.

Three rules:

  1. The secret is Base64-encoded. Base64-decode it before using it as the HMAC key. (The #1 cause of "signature never matches.")
  2. Hash the raw bytes of the request body, before any JSON parsing or re-serialization. Re-ordered keys or normalized whitespace will break the comparison.
  3. Use a constant-time comparison to avoid timing attacks: crypto.timingSafeEqual, hmac.compare_digest, or CryptographicOperations.FixedTimeEquals.

Node.js

import crypto from "node:crypto";

const SECRET_B64 = process.env.VAULTN_WEBHOOK_SECRET;

export function verify(req, rawBody) {
  const header = req.headers["x-vaultn-signature"];
  if (!header?.startsWith("sha256=")) return false;

  const expected = crypto
    .createHmac("sha256", Buffer.from(SECRET_B64, "base64"))
    .update(rawBody)
    .digest("hex");

  const received = header.slice("sha256=".length);
  return (
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  );
}

Express tip: Register express.raw({ type: "application/json" }) on the webhook route so req.body is the raw Buffer. Don't use express.json() — it parses and discards the original bytes.

Python

import base64, hmac, hashlib, os

SECRET = base64.b64decode(os.environ["VAULTN_WEBHOOK_SECRET"])

def verify(headers: dict, raw_body: bytes) -> bool:
    header = headers.get("X-VaultN-Signature", "")
    if not header.startswith("sha256="):
        return False
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header[len("sha256="):], expected)

C# (.NET)

using System.Security.Cryptography;
using System.Text;

public static bool Verify(string signatureHeader, byte[] rawBody, string secretB64)
{
    if (!signatureHeader.StartsWith("sha256=")) return false;

    using var hmac = new HMACSHA256(Convert.FromBase64String(secretB64));
    var computed = Convert.ToHexString(hmac.ComputeHash(rawBody)).ToLowerInvariant();
    var received = signatureHeader["sha256=".Length..];

    return CryptographicOperations.FixedTimeEquals(
        Encoding.ASCII.GetBytes(received),
        Encoding.ASCII.GetBytes(computed));
}

5. How deliveries behave

Response handling

Your responseVaultN does
2xxMarks delivery success.
408, 429, 5xx, network/timeout/TLS errorRetries on schedule.
400, 401, 403, 404, 405, 410, 422Marks delivery failed_final immediately. No retries, not replayable.
DNS or certificate errorMarks delivery failed_final immediately.
⚠️

Important: Fatal status codes indicate the endpoint will never accept the request. Don't return 400 from your handler for transient issues — return 5xx so VaultN retries.

Retry schedule

Up to 10 attempts with exponential backoff and ±10% jitter. Total window: ~26 hours from the first failure.

AttemptDelay after previous
21 minute
32 minutes
44 minutes
58 minutes
616 minutes
729 minutes
81 hour
92 hours
1022 hours

After attempt 10 fails, the delivery is marked failed_final and abandoned. Plan your incident response to fit inside that window — to catch up on missed state, reconcile via the VaultN APIs.

Delivery lifecycle state diagram

Idempotency

Because delivery is at-least-once, your handler will see duplicates eventually. Make handlers idempotent:

  • Key on the envelope's id (stable across retries) — not on X-VaultN-Delivery-Id (changes per attempt).
  • Persist seen IDs for at least the retry window (~26 hours). A small cache (Redis with TTL, or a processed_events table with a unique index on id) is enough.
  • Make side effects safe to repeat — UPSERT rather than INSERT; "set status to X" rather than "increment counter."

Ordering

Deliveries are dispatched concurrently and may arrive out of order — even without retries, variable network latency can reorder events. Two common examples:

  • An order moves Pending → BackOrder, then later BackOrder → Completed. You may see the → Completed event first.
  • A SKU goes out of stock and back in within a minute. The "back in stock" event may land first.

Defences: compare the time in the payload against the most recent time you've seen for the same entity. Use explicit previousStatus/newStatus fields rather than inferring from arrival order.

Pause and recovery

  • Pause: toggle the subscription Inactive in the UI. New events stop being delivered.
  • After the retry window: once a delivery hits failed_final, it's abandoned. Reconcile via our APIs.
  • No backfill: webhooks only deliver events that occur after the subscription is created. For historical state, query our APIs.

6. Designing automations

  • 🔍 Event-then-fetch — Use the webhook for the what and identifiers, then call our APIs for full current state. This sidesteps ordering issues at the cost of one extra request.
  • Event-only — For lightweight automations (Slack alerts, simple metrics), the payload alone is enough. Don't over-engineer.
  • 📥 Outbox on your side — Persist verified events to your own outbox table before the worker processes them. You get free replay from your DB, decoupled from VaultN's 26-hour window.
  • 🎯 Subscribe narrowly — Pick only the event types you act on. "Subscribe to everything" generates noise, more retries, and harder debugging. You can change the list any time.

Separate subscriptions per use case. Two automations with different reliability needs? Use two subscriptions. They get independent delivery histories and won't fail-cascade into each other. Also: monitor your side — track deliveries received, signature failures, and processing latency. The most common silent failure is a deploy that breaks signature verification.


7. Event catalogue

For each event: when it fires, what you'd typically build with it, and an example payload. The first example below shows the complete envelope. Subsequent events abbreviate the outer fields with "id": "..." for brevity — the envelope structure (§3) is identical for every event.

Orders

vaultn.order.status.changed

Fires when: an order's status transitions to a new value. Always carries previousStatus and newStatus.

Use it for: syncing order state into your ERP, triggering fulfillment, refund flows, customer notifications on completion.

Status values: Pending, Completed, Cancelled, NoStock, Error, BackOrder, PreOrder. Common transitions: Pending → Completed (completed reservations), Pending → BackOrder / PreOrder (stock not immediate), BackOrder → Completed (fulfilled when stock arrived), Pending → Cancelled (Expired reservations), BackOrder/PreOrder/Completed → Cancelled (Returns).

{
  "id": "f1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "source": "vaultn.platform",
  "specversion": "1.0",
  "type": "vaultn.order.status.changed",
  "subject": "orders/f6a7b8c9-d0e1-2345-fabc-456789012345",
  "time": "2026-04-27T10:32:18Z",
  "datacontenttype": "application/json",
  "data": {
    "orderGuid": "f6a7b8c9-d0e1-2345-fabc-456789012345",
    "clientOrderReference": "ORD-2026-00123",
    "previousStatus": "Pending",
    "newStatus": "Completed"
  }
}

vaultn.order.backorder.fulfilled

Fires when: a backordered line on an order becomes available and is fulfilled.

Use it for: notifying the buyer, releasing the line to delivery, updating dashboards that track backorder ageing.

{
  "id": "...",
  "type": "vaultn.order.backorder.fulfilled",
  "subject": "orders/f6a7b8c9-d0e1-2345-fabc-456789012345",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM",
    "orderGuid": "f6a7b8c9-d0e1-2345-fabc-456789012345",
    "clientOrderReference": "ORD-2026-00123",
    "quantity": 1
  }
}

vaultn.order.preorder.fulfilled

Fires when: a preordered line becomes available and is fulfilled. Same shape as backorder.fulfilled.

Use it for: release-day notifications, kicking off post-purchase email flows.

Inventory

ℹ️

Note: Inventory events (status.changed, stockpool.low, network.low) are emitted by periodic background jobs, not real-time per stock change. Expect a small delay (typically a few minutes) between the actual stock movement and the webhook delivery. This prevents event flapping when stock fluctuates rapidly around a threshold.

vaultn.inventory.status.changed

Fires when: a SKU transitions in or out of stock. Does not fire on every quantity change — only on the in-stock/out-of-stock boundary. currentQuantity is 0 when going out of stock and the newly available quantity when coming back in.

Use it for: flipping the "Buy" button on a storefront, Slack alerts when a hot title goes out, refreshing PDP cache.

{
  "id": "...",
  "type": "vaultn.inventory.status.changed",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM",
    "previousStatus": "Out of Stock",
    "newStatus": "In Stock",
    "currentQuantity": 250,
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456"
  }
}

vaultn.inventory.stockpool.assigned

Fires when: keys are assigned to a stockpool, including the active window (startDate/endDate). If it does not have an endDate then they are available indefinitely.

Use it for: understanding how much additional reserved stock has been allocated to you.

{
  "id": "...",
  "type": "vaultn.inventory.stockpool.assigned",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "stockpoolName": "EU Steam Pool Q1",
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM",
    "quantity": 500,
    "startDate": "2026-03-01T00:00:00Z",
    "endDate": "2026-03-31T23:59:59Z",
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456"
  }
}

vaultn.inventory.stockpool.low

Fires when: a stockpool's available keys drop at or below its configured warning threshold. Fires per crossing event — if stock recovers and falls below again, you'll get another event.

Use it for: auto-requesting more keys, trigger to reach out to the supplier.

{
  "id": "...",
  "type": "vaultn.inventory.stockpool.low",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "stockpoolName": "EU Steam Pool Q1",
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM",
    "currentStock": 45,
    "threshold": 50,
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456"
  }
}

vaultn.inventory.network.low

Fires when: the network key count for a SKU (excluding stockpool keys) drops at or below the configured threshold. Default threshold is 500, server-side configurable.

Use it for: the same patterns as stockpool.low, but for shared-network inventory.

{
  "id": "...",
  "type": "vaultn.inventory.network.low",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM",
    "currentStock": 485,
    "threshold": 500,
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456"
  }
}

SKU

vaultn.sku.shared

Fires when: a SKU is shared with you by a connection.

Use it for: auto-importing the SKU into your catalog, triggering a review workflow.

{
  "id": "...",
  "type": "vaultn.sku.shared",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM"
  }
}

vaultn.sku.deactivated

Fires when: a SKU is deactivated and should no longer be sold.

Use it for: taking the SKU offline in your store, blocking new orders.

{
  "id": "...",
  "type": "vaultn.sku.deactivated",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "sku": "SAG-GOLD-EU-STEAM"
  }
}

Product

vaultn.product.shared

Fires when: a product (parent of SKUs) is shared on a connection.

Use it for: retrieving meta-data for a single product using /api/v4/Product/ instead of daily full catalog requests.

{
  "id": "...",
  "type": "vaultn.product.shared",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition"
  }
}

vaultn.product.removed

Fires when: a product is removed from a connection.

Use it for: removing it from your store.

{
  "id": "...",
  "type": "vaultn.product.removed",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition"
  }
}

vaultn.product.delisting.scheduled

Fires when: a future delisting is scheduled. Carries the planned delistingDate.

Use it for: planning the removal of products and SKUs from store before orders are blocked.

{
  "id": "...",
  "type": "vaultn.product.delisting.scheduled",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "delistingDate": "2026-05-31T23:59:59Z"
  }
}

vaultn.product.delisted

Fires when: a product is delisted (end of life).

Use it for: removing it from your store if not done so already.

{
  "id": "...",
  "type": "vaultn.product.delisted",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition"
  }
}

vaultn.product.info.updated

Fires when: any product metadata field changes. The changedFields list tells you exactly which fields changed so you only re-fetch what you need.

Use it for: keeping your catalog metadata up to date with the latest available information.

{
  "id": "...",
  "type": "vaultn.product.info.updated",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "changedFields": ["description", "releaseDate", "genres"]
  }
}

Pricing

vaultn.price.changed

Fires when: SRP, purchase price, or VAT changes for a product on a connection/region. Carries both previousPricing and newPricing so you can compute deltas without an extra fetch.

Use it for: repricing on your store front, margin alerts, audit logs.

{
  "id": "...",
  "type": "vaultn.price.changed",
  "subject": "products/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "productGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "productId": "PROD-001",
    "productName": "Super Adventure Game - Gold Edition",
    "regionCode": "EU",
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456",
    "previousPricing": {
      "srp": 49.99,
      "purchasePrice": 34.99,
      "priceListVat": 19.0,
      "currencyCode": "EUR"
    },
    "newPricing": {
      "srp": 39.99,
      "purchasePrice": 27.99,
      "priceListVat": 19.0,
      "currencyCode": "EUR"
    }
  }
}

PriceListVat: Changes to PriceListVat are only relevant if you are on a Revenue share business model with your partner.

vaultn.pricelist.assigned

Fires when: a pricelist is assigned to a connection.

Use it for: triggering a full price refresh for products on that connection.

{
  "id": "...",
  "type": "vaultn.pricelist.assigned",
  "subject": "pricelists/b2c3d4e5-f6a7-8901-bcde-f12345678901",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "priceListGuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "priceListName": "EU Standard Pricelist Q1 2026",
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456",
    "connectionName": "Partner Corp"
  }
}

Promotions

vaultn.promotion.shared

Fires when: a promotion is shared with you on a connection. Carries the scheduledStartDate.

Use it for: planning & importing upcoming promotion details using the /api/v4/connection/{connectionGuid}/promotion/{promotionGuid}/discounts endpoint.

⚠️

Note: vaultn.promotion.started and vaultn.promotion.ended are reserved but not yet emitted by the platform. Don't subscribe to them — the API will reject those keys.

{
  "id": "...",
  "type": "vaultn.promotion.shared",
  "subject": "promotions/e5f6a7b8-c9d0-1234-efab-345678901234",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "promotionGuid": "e5f6a7b8-c9d0-1234-efab-345678901234",
    "promotionId": "PROMO-001",
    "promotionName": "Spring Sale 2026",
    "owner": "tenant_test_001",
    "scheduledStartDate": "2026-05-01T00:00:00Z",
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456",
    "connectionName": "Partner Corp"
  }
}

Connection

vaultn.connection.status.changed

Fires when: a connection's status transitions (e.g. Pending → Active, Active → Suspended).

Use it for: opening/closing the integration on your side, alerting account managers.

{
  "id": "...",
  "type": "vaultn.connection.status.changed",
  "subject": "connections/a7b8c9d0-e1f2-3456-abcd-567890123456",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456",
    "connectionName": "Partner Corp",
    "previousStatus": "Pending",
    "newStatus": "Active"
  }
}

vaultn.connection.settings.changed

Fires when: any connection setting changes (revenue share, payment terms, etc.). The changedFields list tells you which settings.

Use it for: auditing changes, syncing terms into your contracts system.

{
  "id": "...",
  "type": "vaultn.connection.settings.changed",
  "subject": "connections/a7b8c9d0-e1f2-3456-abcd-567890123456",
  "time": "2026-04-27T10:32:18Z",
  "data": {
    "connectionGuid": "a7b8c9d0-e1f2-3456-abcd-567890123456",
    "connectionName": "Partner Corp",
    "changedFields": ["revenueShare", "paymentTerms"]
  }
}

8. Secret rotation

Rotate the signing secret when it may have leaked, when a team member with access leaves, or when your security policy requires it.

1. Open the subscription in the UI and click Refresh secret

2. Save the new secret immediately

The new secret is shown once in the response. Store it in your secret manager right away.

3. Update your receiver and deploy

Update your receiver to use the new secret and deploy before the next delivery arrives.

⚠️

Warning: Rotation takes effect immediately — there's no grace window where both the old and new secrets are accepted. Plan a tight cutover: have the new secret ready in your secret store, then rotate, then deploy. Expect a small number of failed deliveries during the gap; they'll be retried automatically.


9. Troubleshooting

SymptomLikely cause & fix
Signature never matchesSecret used as plain text instead of Base64-decoded. Decode first.
Signature matches sometimes, not alwaysBody is being parsed and re-serialized before hashing. Hash the raw bytes.
Deliveries marked failed_final after one attemptYour endpoint replied 400/401/403/404/405/410/422. Those are fatal — no retries. Don't use 400 for transient issues.
Lots of retries, no successEndpoint timing out (>15s). Move work to a queue and respond fast.
Same event handled twiceExpected — at-least-once delivery. Deduplicate on the envelope id.
Two related events processed in wrong orderExpected — not strictly ordered. Use timestamps/state in data.
Header missing on receiverIntermediate proxy/API gateway stripping X-VaultN-* headers.
All deliveries failing after rotationReceiver still has the old secret. Deploy the new one.
No events at all on a new subscriptionConfirm the subscription is Active and the URL is publicly reachable. Webhooks don't backfill — only events after creation are delivered.
Delivery missing from historyCheck you're using the correct tenantId — deliveries are scoped per tenant.

When raising a support ticket, include: the subscription ID, the X-VaultN-Delivery-Id, the X-VaultN-Instance-Id, and the approximate timestamp. Those pin down the exact delivery in our logs.


Need help?

If you have any questions about setting up webhooks or need help with your integration, do not hesitate to reach out. We are happy to walk you through it.

Contact: [email protected]


Did this page help you?