# Retrieve a Bank Account by ID
Source: https://docs.zivio.com/api-reference/bank-accounts/retrieve-a-bank-account-by-id
get /bank_accounts/{id}
# Retrieve all Bank Accounts, filter by query
Source: https://docs.zivio.com/api-reference/bank-accounts/retrieve-all-bank-accounts-filter-by-query
get /bank_accounts
# Retrieve a Bid by ID
Source: https://docs.zivio.com/api-reference/bids/retrieve-a-bid-by-id
get /bids/{id}
# Retrieve and filter bids
Source: https://docs.zivio.com/api-reference/bids/retrieve-and-filter-bids
get /bids
# Retrieve all EOI Responses, filter by query
Source: https://docs.zivio.com/api-reference/eoi-responses/retrieve-all-eoi-responses-filter-by-query
get /eoi_responses
# Retrieve an EOI Response by ID
Source: https://docs.zivio.com/api-reference/eoi-responses/retrieve-an-eoi-response-by-id
get /eoi_responses/{id}
# Retrieve all EOIs, filter by query
Source: https://docs.zivio.com/api-reference/eois/retrieve-all-eois-filter-by-query
get /eois
# Retrieve an EOI by ID
Source: https://docs.zivio.com/api-reference/eois/retrieve-an-eoi-by-id
get /eois/{id}
# Create an Invoice
Source: https://docs.zivio.com/api-reference/invoices/create-an-invoice
post /invoices
# Delete an invoice by ID
Source: https://docs.zivio.com/api-reference/invoices/delete-an-invoice-by-id
delete /invoices/{id}
# Retrieve all invoices, filter by query
Source: https://docs.zivio.com/api-reference/invoices/retrieve-all-invoices-filter-by-query
get /invoices
# Retrieve an invoice by ID
Source: https://docs.zivio.com/api-reference/invoices/retrieve-an-invoice-by-id
get /invoices/{id}
# Download an invoice file
Source: https://docs.zivio.com/api-reference/invoices/retrieve-an-invoice-file
get /invoices/{id}/file
Retrieve an invoice file. This endpoint redirects you to the invoice file for download if it is available. If no file is available, a JSON response with an error message will be returned.
# Update an invoice as paid
Source: https://docs.zivio.com/api-reference/invoices/update-an-invoice-as-paid
put /invoices/{id}/paid
# Update an Invoice by ID
Source: https://docs.zivio.com/api-reference/invoices/update-an-invoice-by-id
patch /invoices/{id}
# Approve a project step by approver ID
Source: https://docs.zivio.com/api-reference/job-approvals/approve-a-job
patch /job_approvals/{id}/approve
# Reject a project step by approver ID with a rejection_reason
Source: https://docs.zivio.com/api-reference/job-approvals/reject-a-job
patch /job_approvals/{id}/reject
# Create a project
Source: https://docs.zivio.com/api-reference/jobs/create-a-project
post /jobs
# Delete a project by ID
Source: https://docs.zivio.com/api-reference/jobs/delete-a-project-by-id
delete /jobs/{id}
# Retrieve a project by ID
Source: https://docs.zivio.com/api-reference/jobs/retrieve-a-project-by-id
get /jobs/{id}
# Retrieve all projects, filter by query
Source: https://docs.zivio.com/api-reference/jobs/retrieve-all-projects-filter-by-query
get /jobs
# Update a project by ID
Source: https://docs.zivio.com/api-reference/jobs/update-a-project-by-id
patch /jobs/{id}
# Upload a file to a project by ID and attachment_scope with optional sensitivity labels
Source: https://docs.zivio.com/api-reference/jobs/upload-a-file
post /jobs/{id}/file_upload
# Create a Milestone
Source: https://docs.zivio.com/api-reference/milestones/create-a-milestone
post /milestones
# Delete a milestone by ID
Source: https://docs.zivio.com/api-reference/milestones/delete-a-milestone-by-id
delete /milestones/{id}
# Retrieve a milestone by ID
Source: https://docs.zivio.com/api-reference/milestones/retrieve-a-milestone-by-id
get /milestones/{id}
# Retrieve all milestones, filter by query
Source: https://docs.zivio.com/api-reference/milestones/retrieve-all-milestones-filter-by-query
get /milestones
# Update a milestone by ID
Source: https://docs.zivio.com/api-reference/milestones/update-a-milestone-by-id
patch /milestones/{id}
# Retrieve all Offers (SoW), filter by query
Source: https://docs.zivio.com/api-reference/offers/retrieve-all-offers-sow-filter-by-query
get /offers
# Retrieve an Offer (SoW) by ID
Source: https://docs.zivio.com/api-reference/offers/retrieve-an-offer-sow-by-id
get /offers/{id}
# Create an Org
Source: https://docs.zivio.com/api-reference/orgs/create-an-org
post /orgs
# Delete an Org by ID
Source: https://docs.zivio.com/api-reference/orgs/delete-an-org-by-id
delete /orgs/{id}
# Retrieve all orgs, filter by query
Source: https://docs.zivio.com/api-reference/orgs/retrieve-all-orgs-filter-by-query
get /orgs
# Retrieve an Org by ID
Source: https://docs.zivio.com/api-reference/orgs/retrieve-an-org-by-id
get /orgs/{id}
# Retrieve cost centers for an Org
Source: https://docs.zivio.com/api-reference/orgs/retrieve-cost-centers-for-an-org
get /orgs/{org_id}/cost_centers
# Retrieve org units for an Org
Source: https://docs.zivio.com/api-reference/orgs/retrieve-org-units-for-an-org
get /orgs/{org_id}/org_units
# Retrieve people in an Org
Source: https://docs.zivio.com/api-reference/orgs/retrieve-people-in-an-org
get /orgs/{org_id}/org_users
# Update an Org by ID
Source: https://docs.zivio.com/api-reference/orgs/update-an-org-by-id
patch /orgs/{id}
# Create a purchase order
Source: https://docs.zivio.com/api-reference/purchase-orders/create-a-purchase-order
post /purchase_orders
# Delete a purchase order by ID
Source: https://docs.zivio.com/api-reference/purchase-orders/delete-a-purchase-order-by-id
delete /purchase_orders/{id}
# Retrieve a purchase order by ID
Source: https://docs.zivio.com/api-reference/purchase-orders/retrieve-a-purchase-order-by-id
get /purchase_orders/{id}
# Retrieve all purchase orders, filter by query
Source: https://docs.zivio.com/api-reference/purchase-orders/retrieve-all-purchase-orders-filter-by-query
get /purchase_orders
# Update a purchase order by ID
Source: https://docs.zivio.com/api-reference/purchase-orders/update-a-purchase-order-by-id
patch /purchase_orders/{id}
# Retrieve a sales invoice by ID
Source: https://docs.zivio.com/api-reference/sales-invoices/retrieve-a-sales-invoice-by-id
get /sales_invoices/{id}
# Retrieve all sales invoices, filter by query
Source: https://docs.zivio.com/api-reference/sales-invoices/retrieve-all-sales-invoices-filter-by-query
get /sales_invoices
# Retrieve a sales milestone by ID
Source: https://docs.zivio.com/api-reference/sales-milestones/retrieve-a-sales-milestone-by-id
get /sales_milestones/{id}
# Retrieve all sales milestones, filter by query
Source: https://docs.zivio.com/api-reference/sales-milestones/retrieve-all-sales-milestones-filter-by-query
get /sales_milestones
# Retrieve a skill category by ID
Source: https://docs.zivio.com/api-reference/skill-categories/retrieve-a-skill-category-by-id
get /skill_categories/{id}
# Retrieve all skill categories, filter by query
Source: https://docs.zivio.com/api-reference/skill-categories/retrieve-all-skill-categories-filter-by-query
get /skill_categories
# Retrieve a skill taxonomy by ID
Source: https://docs.zivio.com/api-reference/skill-taxonomies/retrieve-a-skill-taxonomy-by-id
get /skill_taxonomies/{id}
# Retrieve all skill taxonomies, filter by query
Source: https://docs.zivio.com/api-reference/skill-taxonomies/retrieve-all-skill-taxonomies-filter-by-query
get /skill_taxonomies
# Retrieve a skill by ID
Source: https://docs.zivio.com/api-reference/skills/retrieve-a-skill-by-id
get /skills/{id}
# Retrieve all skills, filter by query
Source: https://docs.zivio.com/api-reference/skills/retrieve-all-skills-filter-by-query
get /skills
# Retrieve a supplier list by ID
Source: https://docs.zivio.com/api-reference/supplier-lists/retrieve-a-supplier-list-by-id
get /supplier_lists/{id}
# Retrieve all supplier lists, filter by query
Source: https://docs.zivio.com/api-reference/supplier-lists/retrieve-all-supplier-lists-filter-by-query
get /supplier_lists
# Create a Supplier
Source: https://docs.zivio.com/api-reference/suppliers/create-a-supplier
post /suppliers
# Delete a Supplier by ID
Source: https://docs.zivio.com/api-reference/suppliers/delete-a-supplier-by-id
delete /suppliers/{id}
# Retrieve a supplier's catalog
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-catalog
get /suppliers/{id}/catalogs/{catalog_id}
# Retrieve a documents attachments
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-documents-attachments
get /suppliers/{id}/supplier_documents/{document_id}/document_attachments
# Retrieve a supplier's resource
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-resource
get /suppliers/{id}/resources/{resource_id}
# Retrieve a supplier by ID
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-supplier-by-id
get /suppliers/{id}
# Retrieve a supplier's catalogs
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-suppliers-catalogs
get /suppliers/{id}/catalogs
# Retrieve a supplier's documents
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-suppliers-documents
get /suppliers/{id}/supplier_documents
# Retrieve a supplier's resources
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-suppliers-resources
get /suppliers/{id}/resources
# Retrieve a supplier's users
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-a-suppliers-users
get /suppliers/{id}/supplier_users
# Retrieve all suppliers, filter by query
Source: https://docs.zivio.com/api-reference/suppliers/retrieve-all-suppliers-filter-by-query
get /suppliers
# Update a Supplier by ID
Source: https://docs.zivio.com/api-reference/suppliers/update-a-supplier-by-id
patch /suppliers/{id}
# Retrieve a user by ID
Source: https://docs.zivio.com/api-reference/users/retrieve-a-user-by-id
get /users/{id}
# Retrieve all users, filter by query
Source: https://docs.zivio.com/api-reference/users/retrieve-all-users-filter-by-query
get /users
# Retrieve all Variation Orders, filter by query
Source: https://docs.zivio.com/api-reference/variation-orders/retrieve-all-variation-orders-filter-by-query
get /variation_orders
# Retrieve an Variation Order by ID
Source: https://docs.zivio.com/api-reference/variation-orders/retrieve-an-variation-order-by-id
get /variation_orders/{id}
# Test endpoint to validate an API Key
Source: https://docs.zivio.com/api-reference/welcome/test-endpoint-to-validate-an-api-key
get /welcome
# Introduction
Source: https://docs.zivio.com/introduction
An overview of the Zivio API documentation
### The Zivio API
Welcome to the documentation for the Zivio API. The Zivio platform provides a comprehensive API as part of your package, allowing for seamless interactions with your saved data.
Here you will find information about how to use the API, including:
* [How to authenticate yourself by creating an API Key](/quickstart#creating-an-api-key)
* [How to make an example request](/quickstart#making-an-example-request)
* [Which endpoints are available](/api-reference)
* [The information that will be returned from an endpoint and its format](/api-reference)
### Next Steps
To browse all our available endpoints, or see the details of a particular endpoint, please visit the [API Reference](/api-reference) section.
For setup instructions to help you begin, please visit our [quickstart](/quickstart) section.
# Quickstart
Source: https://docs.zivio.com/quickstart
A set of instructions to help you begin
### Getting Started
To interact with the Zivio API, you first need to create an API key within your Zivio platform. You can find out how to do this is in the following section.
### Creating an API Key
First, navigate to the 'Admin' tab in your navigation menu, and click on 'Admin Dashboard'. There you will see a list of options, as in the image below:
Click 'API Keys', which will take you to the below screen:
Click 'Add API Key', and you will be presented with the screen below. Type a name that will help you identify your API Key, and click the 'Create' button.
You will be redirected back to the 'API Keys' page when your new key has been successfully created. Note the flash message that appears:
Please note down the token value, as you will need it when making requests in the next section.
### Making an Example Request
Before making a request to the Zivio API, make sure you have noted down your token value.
To learn how to retrieve your API Key token value from the platform, please see the [Creating an API Key](/quickstart#creating-an-api-key) section above.
When making a request, ensure you have set your token value in the headers like so:
```
{
X-API-Key:
}
```
This will ensure that you are authenticated correctly.
Now, choose the correct method, (e.g. `GET`) and make your request to your normal Zivio URL, adding `/api/v3` (e.g. `https://demo.zivio.com/api/v3`) and the endpoint that you would like to access.
For example, if you are trying to fetch all bank accounts, you would make a `GET` request to `https://demo.zivio.com/api/v3/bank_accounts`.
For more information, including the structure of the response for each endpoint, please see the [api-reference](/api-reference) section.
# Delivery & Retries
Source: https://docs.zivio.com/webhooks/delivery-and-retries
How Zivio delivers events and handles failures
### Successful delivery
A delivery is considered successful when your endpoint returns a `2xx` status code within the 30-second timeout. Once a delivery succeeds, no further attempts are made for that event.
### Retries
If a delivery fails, Zivio automatically retries it on a back-off schedule — frequent at first to catch brief outages, then spaced further apart. There are up to **10 attempts** (the first attempt plus 9 retries) spread over roughly **3 days**.
A small random variation is added to each retry interval to avoid sudden bursts of traffic to your endpoint.
#### What counts as a failure
| Response | Behaviour |
| --------------------- | ---------------------------------------------------- |
| `2xx` | Success — no retry. |
| `5xx`, `408`, `429` | Temporary failure — the delivery is retried. |
| Other `4xx` | Permanent failure — the delivery is **not** retried. |
| Timeout / no response | Temporary failure — the delivery is retried. |
A `3xx` redirect is not followed and is treated as a delivery problem to fix on your side. Point your webhook directly at the final URL.
Returning a `2xx` quickly is the best way to avoid unnecessary retries. If you cannot finish processing within 30 seconds, acknowledge the request immediately and process it asynchronously.
### Ordering and duplicates
* **Ordering is not guaranteed.** Because of retries and independent delivery, events may arrive out of order. Use the `occurred_at` field in the payload if order matters to you.
* **Delivery is at-least-once.** An event may occasionally be delivered more than once. De-duplicate using the `webhook-id` header (also the `id` in the payload).
### Auto-disabling
If a webhook keeps failing for a sustained period — roughly **5 days of continuous failure** with no successful delivery — Zivio automatically disables it to stop sending to an endpoint that is clearly not working. When this happens, the admins who manage webhooks are notified by email.
A disabled webhook stops receiving events until it is re-enabled from the Webhooks admin. Before re-enabling, make sure the underlying issue (an unreachable endpoint, an expired certificate, a changed secret, and so on) has been resolved.
A webhook is also automatically disabled if the admin account that owns it is deleted, since the webhook can no longer determine what data it is permitted to send. If you need the integration to continue, recreate the webhook under an active admin account.
### Reviewing and replaying deliveries
The Webhooks admin records every delivery attempt. From the **Deliveries** view you can:
* Filter deliveries by webhook, event type, status, event ID, and date range.
* Open any delivery to inspect the request and response details.
* **Resend** a past delivery if your endpoint missed it or you need to reprocess it.
A resent event is delivered as a **new** event with a new ID, so a receiver that de-duplicates by event ID will treat it as separate from the original. The delivery log keeps the original and its resends linked together so you can follow the full history.
### Delivery history retention
Delivery records are retained for a limited window (30 days by default) and then automatically removed. Export or store anything you need to keep for longer in your own systems.
# Event Types
Source: https://docs.zivio.com/webhooks/event-types
The events you can subscribe to with webhooks
### Event naming
Every event has a type in the form `resource.action` — for example `invoice.paid` or `offer.accepted`. This value appears in the `type` field of the [payload](/webhooks/payloads) and is how your integration decides what to do with each event.
When creating or editing a webhook you can subscribe to any combination of the events below — individually, by category, or all at once.
The data included with an event reflects the permissions of the webhook's owner. You will only receive events for resources the owner is allowed to see. See [Getting Started](/webhooks/getting-started#what-your-webhook-will-receive).
### Available events
| Event | Description |
| ---------------------- | --------------------------- |
| `bank_account.created` | A bank account was created. |
| `bank_account.updated` | A bank account was updated. |
| Event | Description |
| -------------- | ------------------- |
| `bid.created` | A bid was created. |
| `bid.accepted` | A bid was accepted. |
| Event | Description |
| --------------- | ---------------------------------------- |
| `eoi.approved` | An expression of interest was approved. |
| `eoi.cancelled` | An expression of interest was cancelled. |
| `eoi.closed` | An expression of interest was closed. |
| `eoi.rejected` | An expression of interest was rejected. |
| Event | Description |
| -------------------------- | --------------------------------------------------------- |
| `eoi_response.new` | A new response to an expression of interest was received. |
| `eoi_response.shortlisted` | An EOI response was shortlisted. |
| `eoi_response.rejected` | An EOI response was rejected. |
| Event | Description |
| ------------------- | ------------------------------ |
| `invoice.created` | An invoice was created. |
| `invoice.approved` | An invoice was approved. |
| `invoice.paid` | An invoice was marked as paid. |
| `invoice.cancelled` | An invoice was cancelled. |
| Event | Description |
| --------------------- | -------------------------- |
| `milestone.submitted` | A milestone was submitted. |
| `milestone.approved` | A milestone was approved. |
| `milestone.rejected` | A milestone was rejected. |
| Event | Description |
| -------------------------- | ------------------------------------ |
| `offer.created` | An offer was created. |
| `offer.accepted` | An offer was accepted. |
| `offer.declined` | An offer was declined. |
| `offer.onboarding` | An offer moved to onboarding. |
| `offer.approval_submitted` | An offer was submitted for approval. |
| `offer.approved` | An offer was approved. |
| Event | Description |
| -------------------- | -------------------------------- |
| `project.created` | A project was created. |
| `project.approved` | A project was approved. |
| `project.hired` | A project made a hire. |
| `project.completed` | A project was completed. |
| `project.reopened` | A project was reopened. |
| `project.incomplete` | A project was marked incomplete. |
| `project.cancelled` | A project was cancelled. |
| Event | Description |
| --------------------------- | ------------------------------ |
| `talent_pool.joined` | Someone joined a talent pool. |
| `talent_pool.left` | Someone left a talent pool. |
| `talent_pool.sync_enabled` | Talent pool sync was enabled. |
| `talent_pool.sync_disabled` | Talent pool sync was disabled. |
| `talent_pool.deleted` | A talent pool was deleted. |
| Event | Description |
| ------------------------ | ------------------------------- |
| `user.registered` | A user registered. |
| `user.client_registered` | A client user registered. |
| `user.worker_registered` | A worker user registered. |
| `user.profile_completed` | A user completed their profile. |
| `user.deleted` | A user was deleted. |
| Event | Description |
| ------------------------------------ | --------------------------------------------- |
| `variation_order.created` | A variation order was created. |
| `variation_order.updated` | A variation order was updated. |
| `variation_order.accepted` | A variation order was accepted. |
| `variation_order.declined` | A variation order was declined. |
| `variation_order.approval_submitted` | A variation order was submitted for approval. |
| `variation_order.approved` | A variation order was approved. |
There is also a special `webhook.test` event you can trigger manually to verify your endpoint. It is never sent automatically. See [Getting Started](/webhooks/getting-started#sending-a-test-event).
# Getting Started
Source: https://docs.zivio.com/webhooks/getting-started
Create and manage webhooks from your Zivio platform
### Before you begin
Webhooks are managed from the **Admin** area of your Zivio platform. You will need:
* An admin account with permission to manage webhooks. If you cannot see the Webhooks section, ask your platform administrator to grant you access.
* A publicly reachable **HTTPS** endpoint that can accept `POST` requests and return a `2xx` response.
Webhook URLs must be public HTTPS addresses. Internal, private, or local addresses are not permitted.
### Creating a webhook
Navigate to the **Admin** area and select **Webhooks**.
Choose **Add Webhook** and complete the form:
* **Name** — a label to help you identify the webhook.
* **URL** — the HTTPS endpoint that will receive events.
* **Description** — optional notes about what this webhook is for.
* **Events** — tick the events you want to receive. You can select individual events, an entire category at once, or all events.
When the webhook is created, Zivio shows you a **signing secret** that begins with `whsec_`.
The full signing secret is shown only once, at creation. Copy it and store it securely — you will need it to [verify incoming requests](/webhooks/verifying-signatures). If you lose it, you can rotate the secret to generate a new one.
After this, only the last few characters of the secret are ever displayed, so you can identify which secret is in use without exposing it.
### What your webhook will receive
The data in each event payload reflects the permissions of the admin who created the webhook — the **owner**. A webhook only receives events for resources its owner is allowed to see, and the payload is shaped exactly as that owner would see it through the [v4 API](/api-reference). Keep this in mind when choosing who creates a webhook.
For a service-style integration, create the webhook from an admin account that has the level of access your integration needs — no more, no less.
### Sending a test event
Once your webhook is set up, you can send a **test event** to confirm your endpoint is reachable and your signature verification works. A test event has the type `webhook.test` and a simple placeholder payload, so it is safe to send at any time without affecting real data.
### Managing an existing webhook
From the Webhooks admin you can:
* **Edit** a webhook's name, URL, description, and subscribed events.
* **Rotate the signing secret** — this immediately invalidates the old secret and shows you a new one once. Your endpoint will reject requests until you update the stored secret on your side, so rotate during a maintenance window if possible.
* **Disable** a webhook to stop deliveries without deleting its history.
* **Review the health** of each webhook — recent delivery status and success rates are shown on the webhooks list.
### Reviewing deliveries
Every attempt to send an event is recorded. From the **Deliveries** view you can browse and filter deliveries by webhook, event type, status, and date, and open any delivery to inspect its request and response details. You can also **resend** a past delivery if needed.
When you resend an event, your endpoint receives it as a brand-new event with a new ID. If your system de-duplicates by event ID, it will treat the resend as a separate event.
### Next steps
See everything you can subscribe to.
Secure your endpoint against forged requests.
# Webhooks Overview
Source: https://docs.zivio.com/webhooks/overview
Receive real-time notifications when things happen in Zivio
### What are Webhooks?
Webhooks let Zivio notify your systems automatically the moment something happens on your platform — a new invoice is created, an offer is accepted, a milestone is approved, and more. Instead of repeatedly calling the API to check for changes (polling), you give Zivio a URL and we send an HTTP `POST` request to it whenever a relevant event occurs.
Each notification (a "delivery") contains a JSON payload describing what happened, including the same data you would receive from the equivalent [API Reference](/api-reference) endpoint.
Webhooks are ideal for keeping an external system — such as an ERP, finance tool, or data warehouse — in sync with Zivio in near real time.
### How it works
In your Zivio platform, you create a webhook by providing a destination URL and choosing which events you want to be notified about.
Something happens in Zivio that matches one of your subscribed events — for example, an invoice is paid.
Zivio sends a signed `POST` request containing a JSON payload to your URL.
Your endpoint verifies the signature, processes the event, and returns a `2xx` response to confirm receipt.
### Key concepts
An action that happens in Zivio, such as `invoice.created` or `offer.accepted`. See the full list in [Event Types](/webhooks/event-types).
A destination you configure to receive events: a URL, a description, and the set of events it is subscribed to.
A single attempt to send one event to your URL. Deliveries are signed, automatically retried on failure, and recorded in a delivery log you can review.
A secret unique to each webhook, used to sign every request so you can verify it genuinely came from Zivio.
### Built on an open standard
Zivio webhooks follow the [Standard Webhooks](https://www.standardwebhooks.com/) specification. This means signatures and headers are compatible with a wide range of open-source libraries, so verifying and consuming events is straightforward in most languages.
### Next steps
Create and manage your first webhook.
Browse every event you can subscribe to.
Understand the structure of a webhook request.
Confirm that requests really come from Zivio.
# Payloads
Source: https://docs.zivio.com/webhooks/payloads
The structure of a webhook request from Zivio
### Request format
Zivio delivers each event as an HTTP `POST` request to your configured URL with a JSON body and a `Content-Type` of `application/json`. Requests are sent with the user agent `Zivio-Webhooks/1.0`.
### Headers
Every request includes the following headers, which you use to [verify the signature](/webhooks/verifying-signatures):
A unique identifier for this event. Use it to de-duplicate events if your endpoint might receive the same delivery more than once.
The time the event was sent, as a Unix timestamp (seconds).
The signature(s) for this request, used to confirm it came from Zivio. See [Verifying Signatures](/webhooks/verifying-signatures).
### Payload structure
The JSON body follows a consistent envelope:
The unique identifier for the event. This matches the `webhook-id` header.
The event type, such as `invoice.paid`. See [Event Types](/webhooks/event-types).
An ISO 8601 timestamp for when the event occurred.
The API version that shapes the `data` object — currently `v4`.
The resource the event relates to, serialized exactly as it would be returned by the equivalent [v4 API](/api-reference) endpoint. The fields included reflect the permissions of the webhook's owner.
### Example payload
```json theme={null}
{
"id": "evt_a1b2c3d4e5f6",
"type": "invoice.paid",
"occurred_at": "2026-06-10T14:32:05Z",
"api_version": "v4",
"data": {
"id": 12345,
"status": "paid"
// ...the rest of the invoice, matching the v4 Invoices API
}
}
```
To know exactly which fields appear in `data` for a given event, look up the matching resource in the [API Reference](/api-reference). For example, an `invoice.*` event's `data` mirrors the [Invoices](/api-reference/invoices/retrieve-an-invoice-by-id) response.
### Test event payload
A manually triggered test event uses the type `webhook.test` and a placeholder body, so you can validate your endpoint without referencing real data:
```json theme={null}
{
"id": "evt_test_0000",
"type": "webhook.test",
"occurred_at": "2026-06-10T14:32:05Z",
"api_version": "v4",
"data": { "test": true }
}
```
### Responding to a request
Your endpoint should:
* Return a `2xx` status code to acknowledge successful receipt.
* Respond quickly — requests time out after **30 seconds**. If your processing is slow, acknowledge the request first and do the work asynchronously.
* Treat delivery as **at-least-once**: the same event may occasionally arrive more than once. De-duplicate using the `webhook-id` / `id` value.
Any non-`2xx` response (or a timeout) is treated as a failed delivery and may be [retried](/webhooks/delivery-and-retries).
# Verifying Signatures
Source: https://docs.zivio.com/webhooks/verifying-signatures
Confirm that webhook requests genuinely come from Zivio
### Why verify?
Your webhook URL is a public endpoint, so anyone could attempt to send requests to it. Every request from Zivio is **signed** with your webhook's signing secret. By verifying the signature before acting on a request, you can be confident it genuinely came from Zivio and was not tampered with in transit.
Always verify the signature before trusting or processing a webhook payload.
### Use a Standard Webhooks library (recommended)
Zivio signs requests following the [Standard Webhooks](https://www.standardwebhooks.com/) specification. The easiest and safest way to verify is with one of the [official open-source libraries](https://github.com/standard-webhooks/standard-webhooks/tree/main/libraries), available for many languages. Pass your signing secret and the request headers, and the library handles verification — including protection against replay attacks — for you.
```javascript Node.js theme={null}
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.ZIVIO_WEBHOOK_SECRET); // "whsec_..."
// `body` must be the raw request body string, not a parsed object
const payload = wh.verify(body, {
"webhook-id": req.headers["webhook-id"],
"webhook-timestamp": req.headers["webhook-timestamp"],
"webhook-signature": req.headers["webhook-signature"],
});
// `payload` is now the verified, parsed event
```
```python Python theme={null}
from standardwebhooks import Webhook
wh = Webhook(os.environ["ZIVIO_WEBHOOK_SECRET"]) # "whsec_..."
# `body` must be the raw request body bytes/string, not a parsed object
payload = wh.verify(body, {
"webhook-id": headers["webhook-id"],
"webhook-timestamp": headers["webhook-timestamp"],
"webhook-signature": headers["webhook-signature"],
})
```
### How the signature works
If you prefer to verify manually, the scheme is straightforward:
Concatenate the `webhook-id`, the `webhook-timestamp`, and the raw request body, separated by full stops:
```
{webhook-id}.{webhook-timestamp}.{raw-body}
```
Your signing secret is the part after the `whsec_` prefix, which is **Base64-encoded**. Base64-decode it to get the key, then compute an `HMAC-SHA256` of the signed content using that key. Base64-encode the result.
The `webhook-signature` header contains a space-separated list of signatures, each prefixed with a version, e.g. `v1,`. Compare your computed value against the `v1` signature using a constant-time comparison.
Reject requests whose `webhook-timestamp` is too far from the current time (a tolerance of around five minutes is typical) to defend against replay attacks.
You must use the **raw** request body exactly as received to compute the signature. Re-serializing a parsed JSON object can change whitespace or key order and will cause verification to fail.
### Manual verification example
```javascript Node.js theme={null}
import crypto from "crypto";
function verify(rawBody, headers, signingSecret) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatureHeader = headers["webhook-signature"];
// 1. Build the signed content
const signedContent = `${id}.${timestamp}.${rawBody}`;
// 2. Compute the expected signature
const key = Buffer.from(signingSecret.replace(/^whsec_/, ""), "base64");
const expected = crypto
.createHmac("sha256", key)
.update(signedContent)
.digest("base64");
// 3. Compare against each signature in the header (scheme: "v1,")
const passed = signatureHeader.split(" ").some((part) => {
const [, sig] = part.split(",");
return (
sig &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
);
});
if (!passed) throw new Error("Invalid webhook signature");
}
```
### Rotating your secret
If your secret is ever exposed, rotate it from the Webhooks admin. Rotation takes effect **immediately**: the old secret stops working at once and a new one is shown to you a single time. Update the stored secret in your endpoint as soon as you rotate, or verification will fail in the interim.