# 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: Admin Dashboard Click 'API Keys', which will take you to the below screen: API Keys 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. New API Keys You will be redirected back to the 'API Keys' page when your new key has been successfully created. Note the flash message that appears: New Api Key Flash 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.