> ## Documentation Index
> Fetch the complete documentation index at: https://developers.pleo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Detect Draft Vendors

> For Vendor Creation, how to reliably detect new DRAFT vendors created directly in Pleo, using webhooks or polling.

This how-to explains how an integration detects a `DRAFT` Vendor created directly in Pleo, ready to be created in the Accounting System.

Run this after [How to Create Vendors in Pleo](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendors-in-pleo), which covers the bookkeeper action that produces the `DRAFT` Vendor.

A `DRAFT` Vendor exists when a bookkeeper adds a new Vendor in Pleo that has no matching record in the AS yet. It has no `code` or `externalId` until the integration creates the record in the AS and activates it.

Your integration must:

* Detect new `DRAFT` Vendors, ideally the moment they are created
* Avoid re-detecting a Vendor that has already been created in the AS

## Prerequisites

Before you begin:

* You have completed [How to Create Vendors in Pleo](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendors-in-pleo), or a bookkeeper has otherwise added a new Vendor directly in Pleo
* You're familiar with [Detect Draft Vendors](/docs/current/integration-design/exports/vendors/integration-design-vendor-creation-detect-draft-vendors)
* Your integration is authenticated using one of the [supported authentication methods](/docs/current/integration-design/auth/integration-design-auth-overview#authentication-policy-overview)
* Your integration can call Pleo's Vendors API endpoints
* Webhooks or polling are configured

## Scenario

This how-to continues from the [How to Create Vendors in Pleo](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendors-in-pleo) scenario: a bookkeeper added "Greenfield Logistics" as a new Vendor in Pleo, without it existing yet in the Accounting System.

## Steps

### 1. Detect New Draft Vendors

Two discovery strategies are supported.

#### Option A: Webhook Trigger (Recommended)

Subscribe to the [`v1.vendor.created`](/reference/webhooks/webhook-events) webhook. See [Configuring Webhooks During Integration](/reference/webhooks/overview-webhooks#configuring-webhooks-during-integration) for subscription setup.

When the event is received, its payload already contains the Vendor's details, so no additional fetch is required.

Remember

Webhooks can be lost. Pleo retries a failed delivery for up to about 27.5 hours, then stops (see [Webhook Notification Delivery Retries](/reference/webhooks/overview-webhooks#webhook-notification-delivery-retries)). If your endpoint is down longer than that, for example during an extended outage, that webhook is gone for good, and you have no way to know the Vendor was created.

Run a low-frequency backstop poll (for example, hourly) using Option B below, even when webhooks are your main detection method. It catches any `DRAFT` Vendor whose webhook was never delivered.

#### Example Webhook Payload

```json theme={null}
{
  "data": {
    "id": "c27ed4d6-3d3d-458e-8f3f-735fdf32a3bc",
    "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
    "name": "Greenfield Logistics",
    "country": "GB",
    "registrationNumber": "",
    "taxRegistrationNumber": "GB987654321",
    "code": null,
    "state": "DRAFT",
    "defaultCurrency": "GBP",
    "createdBy": "22aff90a-a53d-4f0c-851f-dd4cc85ac947",
    "createdAt": "2026-07-08T10:05:43.530450712Z",
    "updatedAt": "2026-07-08T10:05:43.530450712Z",
    "deletedAt": null
  },
  "eventType": "v1.vendor.created",
  "eventId": "13d8550d-dd22-46e9-ae18-c171846ba373"
}
```

Note

In the example above, `registrationNumber` is `""` (empty string), but `code` is `null`. Both mean the same thing: no value.

Pleo uses `""` for optional text fields that were never filled in, like `registrationNumber` and `taxRegistrationNumber`. It uses `null` for identifier fields not yet assigned, like `code` before activation.

When reading a Vendor, treat `""` and `null` the same way. When writing these fields to the Accounting System, check its own rules first: some systems treat `""` and `null` differently, or reject one of them. Normalise the value to match, rather than passing it through unchanged.

Proceed straight to [How to Create a Vendor in the AS](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendor-in-as) using the Vendor `id` and details from the payload.

#### Option B: Polling (fallback, only if webhooks cannot be supported)

**API Endpoint**: POST [`/v1/vendors:search`](/reference/external-vendors/fetches-vendors-by-search-criteria)

Periodically call the search endpoint filtering on `states: ["DRAFT"]`, on the [required polling interval of every 5 minutes](/docs/current/integration-design/exports/vendors/integration-design-vendor-creation-detect-draft-vendors#polling-fallback-only-if-webhooks-cannot-be-supported).

**Example Pseudo:**

```pseudo theme={null}
draftVendors = []
cursor = null

do:
    page = searchVendors(states=["DRAFT"], after: cursor)
    draftVendors.extend(page.data)
    cursor = page.pagination.endCursor
while page.pagination.hasNextPage

for vendor in draftVendors:
    createAndActivate(vendor)
```

Fetch every page before processing. Stopping after the first page can leave DRAFT Vendors undetected until the next poll cycle.

Remember

A poll with multiple pages is not an instant snapshot. Fetching all the pages takes time.

If a bookkeeper adds a new Vendor while you're still fetching pages, it might land on a page you've already passed. This poll can miss it.

That Vendor isn't lost: the next poll cycle picks it up, or the webhook catches it right away if you're also subscribed.

Don't treat one poll's results as a complete list of every `DRAFT` Vendor that existed the moment the poll started. It's only complete as of when each page was actually fetched.

#### Example Request

**OAuth 2.0**

```bash theme={null}
curl -X POST "https://external.staging.pleo.io/v1/vendors:search?company_id=12abc3d4-e567-890e-1234-abc56e78fabc" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json;charset=UTF-8" \
  -d '{
        "states": ["DRAFT"]
      }'
```

**API Key**

```bash theme={null}
curl --request POST \
  -u "pls_1ab2cd3e4f5g6h7a89b012c34de56f78_gabc90:" \
  -H "Accept: application/json;charset=UTF-8" \
  -H "Content-Type: application/json;charset=UTF-8" \
  "https://external.staging.pleo.io/v1/vendors:search?company_id=12abc3d4-e567-890e-1234-abc56e78fabc" \
  -d '{
        "states": ["DRAFT"]
      }' \
| jq
```

#### Example Response

```json theme={null}
{
  "data": [
    {
      "id": "c27ed4d6-3d3d-458e-8f3f-735fdf32a3bc",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "name": "Greenfield Logistics",
      "country": "GB",
      "registrationNumber": "",
      "taxRegistrationNumber": "GB987654321",
      "state": "DRAFT",
      "defaultCurrency": "GBP",
      "createdAt": "2026-07-08T10:05:43.530450712Z",
      "updatedAt": "2026-07-08T10:05:43.530450712Z"
    }
  ],
  "pagination": {
    "hasPreviousPage": false,
    "hasNextPage": false,
    "endCursor": "ZMKHP6I3F5E43BOVXNINZOZGM4",
    "total": 1
  }
}
```

Note

The response's `pagination` object (shown above) tells you whether more `DRAFT` Vendors exist beyond this page, and how to fetch them. This is what the loop in the pseudo-code above is reading from.

Check `hasNextPage` to know when to stop paginating. That's the only reliable signal.

`endCursor` is not a reliable signal on its own: it can still be a non-null string on the last page, as the example above shows.

While `hasNextPage` is `true`, pass `endCursor` as the `after` parameter on your next request, to fetch the next page.

Note

This polling method (Option B) only checks every [5 minutes](/docs/current/integration-design/exports/vendors/integration-design-vendor-creation-detect-draft-vendors#polling-fallback-only-if-webhooks-cannot-be-supported). A bookkeeper can add a new Vendor in Pleo and it can sit undetected for up to 5 minutes, until your next poll runs.

Prefer the webhook method (Option A above) where you can. It notifies you the moment the Vendor is created, instead of making you wait for the next poll.

#### What it looks like in Pleo Web App

On the expense from [How to Create Vendors in Pleo](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendors-in-pleo):

1. Select **Accounting** from the main left-hand navigation.
2. Select **Export** from the sub-menu.
3. Click on the same row to reopen the details panel for that expense.

"Greenfield Logistics" is selected on the Vendor field, with a note that the Vendor is still being created. This is the `DRAFT` state your integration needs to detect:

![Vendor field on an expense showing newly created Greenfield Logistics, not yet linked to the Accounting System](https://mintcdn.com/pleo-61d4d38b/OQXz7YYPcGlsTpec/images/current/accounting-integrations/imports/vendor-sync/ui-vendors-active-activate-pending-greenfield-logistics.png?fit=max\&auto=format\&n=OQXz7YYPcGlsTpec\&q=85\&s=1c46d144c7611fb5e8c42d82f0ce157a)

***

## Handling Edge Cases

Neither detection path guarantees a `DRAFT` Vendor is seen only once, and there is no batching for Vendor Creation:

* **Webhook redelivery**: the webhook provider may redeliver the same `v1.vendor.created` event. Persist the `eventId` from the payload and skip any event you have already processed.
* **Overlapping polls, or webhook and polling running together**: a poll cycle can re-detect a `DRAFT` Vendor that is already being created/activated from a previous poll or from the webhook path, because the Vendor genuinely still has `state: DRAFT` until activation completes. Filtering on `state: DRAFT` does not prevent this. Before creating or activating a Vendor, check whether that Vendor id is already marked in-flight (for example, a persisted "processing" record keyed on the Pleo Vendor id), and skip it if so.
* **No batching**: process each detected `DRAFT` Vendor as its own independent item. A delay or failure creating one Vendor must not block any other.

Remember

Detecting a `DRAFT` Vendor must not change its actual state in Pleo. This step is read-only.

Only two actions ever change a Vendor's state: creating it in the AS, and calling `:activate`. Both happen in the next how-to, not here.

If you need to track that a Vendor is already being processed (see the overlapping-polls case above), keep that as your own separate record. Don't represent it using the Vendor's real state field in Pleo.

***

## Result

After completing this step:

* A `DRAFT` Vendor has been detected, either via webhook or polling
* The Vendor's `name`, `country`, `defaultCurrency`, `registrationNumber`, and `taxRegistrationNumber` are available
* The integration is ready to create the corresponding record in the AS

***

## What Comes Next?

[How to Create a Vendor in the AS →](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendor-in-as)

***

this how-to is part of:

[Vendor Creation Workflow Guide](/docs/current/guides/vendor-creation-workflow-guide)

***

## Related Reading

* [How to Create Vendors in Pleo](/docs/current/how-tos/accounting-integrations/vendors/how-to-create-vendors-in-pleo)
* [Detect Draft Vendors Integration Design](/docs/current/integration-design/exports/vendors/integration-design-vendor-creation-detect-draft-vendors)
* [Platform Capabilities: Vendor Creation](/docs/current/platform/exports/vendors/vendor-creation-overview)
* [Configuring Webhooks During Integration](/reference/webhooks/overview-webhooks#configuring-webhooks-during-integration)

***
