# Sovos Webhooks

Sovos pushes Indirect Tax API (e-invoicing) notifications to your ERP endpoint
through Svix. Svix signs each webhook, retries failed
deliveries, and hosts the **Consumer App Portal** where you add endpoints, copy
the signing secret, and inspect delivery logs. This skill covers the Indirect
Tax API / e-invoicing webhooks only; other Sovos products are out of scope.

## When to Use This Skill

* How do I receive Sovos e-invoicing webhooks?
* How do I verify Sovos webhook signatures (`svix-signature`)?
* How do I handle a Sovos `document.received` notification?
* Why is my Sovos webhook signature verification failing?
* Where do I find the Sovos webhook signing secret (Svix Consumer App Portal)?
* How do I download the application response from `contentUrl`?

## Verification (core)

Sovos does not define its own signature scheme: its docs tell you to verify with
the Svix library or Svix's manual procedure. The header names and the `whsec_`
secret format below come from Svix's verification guide,
which Sovos links to; Sovos's own pages don't restate them. Each request carries
three headers:

```
svix-id: msg_2b1c...            # unique message id
svix-timestamp: 1770290583      # Unix seconds
svix-signature: v1,g0hM9SsE...  # space-delimited "v1,<base64 sig>" entries

```

Signed content is `${svix-id}.${svix-timestamp}.${raw_body}`, HMAC-SHA256 keyed
with the base64-decoded bytes after the `whsec_` prefix, base64-encoded. The
official `svix` SDK (Node and Python) does all of this, including the 5-minute
timestamp tolerance, rotation (multiple signatures), and constant-time compare:

```javascript
import { Webhook } from 'svix';

const wh = new Webhook(process.env.SOVOS_WEBHOOK_SECRET); // "whsec_..."
wh.verify(rawBody, {                 // rawBody: the raw Buffer/string, never re-serialized JSON
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
}); // throws WebhookVerificationError on failure; returns undefined in svix 2.x

const notification = JSON.parse(rawBody.toString('utf8'));
// notification.eventType === 'document.received', notification.metadata.documentId, ...

```

Python uses the same API: `from svix.webhooks import Webhook` and
`Webhook(secret).verify(body_bytes, headers)`. For the manual (no-SDK) algorithm,
see [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sovos-webhooks/references/verification.md).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sovos-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sovos-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/sovos-webhooks/examples/fastapi/).

## Common Event Types

The event type is the top-level **`eventType`** field (not `type`). You choose
which events are sent on the **Sovos side** (Compliance Network UI or
`POST /v2/configurations/workspaces/settings`); the Svix portal cannot add or
filter events. Event names that appear in Sovos's documentation:

| `eventType` | When it is sent |
| --- | --- |
| `document.received` | A document notification was received during processing (the documented payload example) |
| `document.completed` | A document finished its lifecycle |
| `workflow.completed` | A processing workflow for a document completed |
| `system.error` | Processing failed with a system error |

Sovos does not publish a full catalog on the webhooks pages, so keep a default
branch for event types you have not mapped. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sovos-webhooks/references/overview.md) for the payload fields.

## Environment Variables

```bash
# Endpoint signing secret from the Svix Consumer App Portal (starts with whsec_)
SOVOS_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 sovos --path /webhooks/sovos

```

Add the tunnel URL as an endpoint in the Svix Consumer App Portal, then use the
portal's **Testing** tab to send an example event.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sovos-webhooks/references/overview.md) - How Sovos notifications work, event types, payload fields
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sovos-webhooks/references/setup.md) - Configure the ERP system, webhook communication method, and Consumer App Portal
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/sovos-webhooks/references/verification.md) - Svix signature verification (SDK and manual) and gotchas