Webhooks

Introduction

Webhooks let us notify your systems when something happens, without you having to poll our API. Instead of repeatedly asking "did anything change yet?", you register an endpoint with Skovik and we send a request to it whenever a subscribed event occurs.

An example use case is to react to export order status updates. Rather than continuously checking if an export order has finished, you subscribe to export_order.completed and get notified when the export is ready.

Webhooks are well suited for low-latency, event-driven integrations. They are not a replacement for the API. Your endpoint should treat the webhook as a notification that something happened, then call the API to fetch the latest state of the related resource.

Payload format

The body of a webhook event is a JSON document. Some events have no related resource, in which case the relationships key is omitted entirely.

{
  "id": "<delivery id, matches webhook-id header>",
  "type": "<event name, for example export_order.created>",
  "timestamp": "<ISO 8601 timestamp>",
  "data": {
    "id": "<event id>",
    "type": "webhook_events",
    "attributes": {
      "created_at": "<ISO 8601 timestamp>",
      "name": "<event name>"
    },
    "links": {
      "self": "/v1/webhook_events/<event id>"
    },
    "relationships": {
      "related_object": {
        "data": {
          "type": "<resource type>",
          "id": "<resource id>"
        },
        "links": {
          "related": "/v1/<resource type>/<resource id>"
        }
      }
    }
  }
}

The payload includes an event name and related resources. To get the current state of the related resource, make a request to the data.relationships.related_object.links.related URL. The webhook intentionally does not embed the full resource. This keeps payloads small and ensures you always act on the current state, not a snapshot that may already be stale by the time you process it.

Example payload

An export_order.completed delivery looks like this:

{
  "id": "60c7d63a8f4c4b9e9c2e1a2b",
  "type": "export_order.completed",
  "timestamp": "2026-05-25T14:30:45Z",
  "data": {
    "id": "8b1d4f122c0e4b1a9f9d3d0e",
    "type": "webhook_events",
    "attributes": {
      "created_at": "2026-05-25T14:30:45Z",
      "name": "export_order.completed"
    },
    "links": {
      "self": "/v1/webhook_events/8b1d4f122c0e4b1a9f9d3d0e"
    },
    "relationships": {
      "related_object": {
        "data": {
          "type": "export_orders",
          "id": "f3a9c7e15b344a8cb6f122b0"
        },
        "links": {
          "related": "/v1/export_orders/f3a9c7e15b344a8cb6f122b0"
        }
      }
    }
  }
}

Managing webhooks

Webhook endpoints are managed from the settings UI. Each endpoint has an associated signing secret used for verifying the authenticity of payloads.

Endpoints must support communication via HTTPS.

🗝

Keep the Secret Safe

The signing secret tied to an endpoint should be treated as securely as your API tokens.
If lost or compromised, you may regenerate the secret from the settings page.

❗️

Regenerating Webhook Secrets

Regenerating a secret invalidates the previous one immediately!
All subsequent delivery attempts, including retries of events that were created before the regeneration, will be signed with the new secret.
Deploy the new secret to your endpoint as soon as possible after regenerating. Attempts made in the interim will fail signature verification and have to wait for the next retry.

If a verification gap from regenerating the secret is unacceptable, an alternative is to register a second endpoint with its own secret, deploy the new secret to your handler, and confirm deliveries from the new endpoint before deleting the old. With two active endpoints, each event will be delivered twice, so your handler should deduplicate on the inner event id from data.id.

Testing an endpoint

To test your webhook, use the special misc.ping event. From the settings page you can send a ping event to any endpoint. Use this to verify that your endpoint can successfully receive, verify, and acknowledge events. A ping can be used even when the endpoint is disabled, but unlike normal events, a ping will not be retried.

The ping event has no related resource, so the relationships key is omitted from the payload:

{
  "id": "1d3b9a440c2f4ad78a1f9e3f",
  "type": "misc.ping",
  "timestamp": "2026-05-25T14:30:45Z",
  "data": {
    "id": "8c2e0c9a4d114d5e91b33a7c",
    "type": "webhook_events",
    "attributes": {
      "created_at": "2026-05-25T14:30:45Z",
      "name": "misc.ping"
    },
    "links": {
      "self": "/v1/webhook_events/8c2e0c9a4d114d5e91b33a7c"
    }
  }
}