Kestra Deel Plugin Documentation
Overview
The Kestra Deel plugin provides integration with the Deel HR and payroll platform. It enables Kestra flows to interact with Deel's API for managing contracts, people, invoices, payments, time off, and organizational data.
Plugin classes are under the io.kestra.plugin.deel package.
Authentication
All tasks and triggers that call the Deel API require an API token.
- Property:
apiToken
- Type:
Property<String>
- Required: Yes for API-enabled tasks and triggers
- Common pattern:
{{ secret('DEEL_API_TOKEN') }}
The API token must possess the appropriate Deel scopes for the intended operations (e.g., timesheets: read, contracts: read, organizations: read, payments: statements: read, invoices: read, lookups: read, worker: read).
Tasks
Contracts
| Task |
Fully Qualified Name |
Description |
| ContractsList |
io.kestra.plugin.deel.contracts.ContractsList |
List contracts with filtering by type, status, legal entity, or team. Supports cursor-based pagination via afterCursor. |
| ContractsGet |
io.kestra.plugin.deel.contracts.ContractsGet |
Retrieve a single contract by its Deel-assigned UUID ID. |
People
| Task |
Fully Qualified Name |
Description |
| PeopleList |
io.kestra.plugin.deel.people.PeopleList |
List people in the organization with employment details. Supports offset-based pagination using limit and offset. Filter by hiring status, hiring type, legal entity, or search term. |
| PeopleGet |
io.kestra.plugin.deel.people.PeopleGet |
Retrieve a single person (worker) by their Deel person ID UUID. |
Invoices
| Task |
Fully Qualified Name |
Description |
| List (Invoices) |
io.kestra.plugin.deel.invoices.List |
List workforce invoices. Supports filtering by status (paid by default, or all), contract ID, and date ranges (issued from/to). Supports offset- and cursor-based pagination. |
Documents (EOR Contract Documents)
| Task |
Fully Qualified Name |
Description |
| List (Documents) |
io.kestra.plugin.deel.documents.List |
List HRX documents shared under an EOR contract. Requires contractId. Supports cursor-based pagination via cursor. |
| Download |
io.kestra.plugin.deel.documents.Download |
Download an HRX document as a PDF. Requires contractId and documentId. The API returns a pre-signed URL valid for 15 minutes; the file content is stored in Kestra storage. Requires contracts: read and worker: read scopes. |
Timesheets
| Task |
Fully Qualified Name |
Description |
| List (Timesheets) |
io.kestra.plugin.deel.timesheets.List |
List timesheets in the account. Supports filtering by contractId, status (approved, declined, not_payable, paid, pending, processing), and date ranges (dateFrom, dateTo). Supports offset-based pagination via limit and offset. |
Payments
| Task |
Fully Qualified Name |
Description |
| GetStatement |
io.kestra.plugin.deel.payments.GetStatement |
Retrieve a payment statement by its unique ID. Includes status, amounts, beneficiary details, and associated settled invoices. Requires payment-statements: read scope. |
Lookups
| Task |
Fully Qualified Name |
Description |
| Get |
io.kestra.plugin.deel.lookups.Get |
Retrieve platform reference data. Supported lookupType values: COUNTRIES, CURRENCIES, JOB_TITLES, SENIORITIES. The JOB_TITLES endpoint paginates via afterCursor. The SENIORITIES endpoint supports isEorContract filter (excludes C-level when true). |
Organization
| Task |
Fully Qualified Name |
Description |
| CostCenters |
io.kestra.plugin.deel.org.CostCenters |
List cost centers for a specific legal entity. Requires legalEntityId. |
| Teams |
io.kestra.plugin.deel.org.Teams |
List teams within the authenticated user's organization. |
| LegalEntities |
io.kestra.plugin.deel.org.LegalEntities |
List legal entities in the account. Supports filtering by country, entityType, legalEntityId, globalPayroll, and includeArchived. Supports cursor-based pagination via cursor. |
| Managers |
io.kestra.plugin.deel.org.Managers |
List managers in the organization. Supports offset-based pagination via limit and offset. |
| Departments |
io.kestra.plugin.deel.org.Departments |
List departments within the authenticated user's organization. |
Triggers
Polling Triggers
| Trigger |
Fully Qualified Name |
Description |
| ContractTrigger |
io.kestra.plugin.deel.contracts.ContractTrigger |
Poll the Contracts endpoint and trigger on contract lifecycle events. Supports configurable events (created, signed, terminated). State (watermark + last-seen statuses) is persisted in the KV store to prevent duplicate emissions. |
| InvoiceIssuedTrigger |
io.kestra.plugin.deel.invoices.InvoiceIssuedTrigger |
Poll invoices and trigger on newly issued events. State is persisted in the KV store so that already-seen invoice IDs are tracked and not re-emitted. |
| PersonTrigger |
io.kestra.plugin.deel.people.PersonTrigger |
Poll the People endpoint and trigger on created people and hiring status changes. State is persisted in the KV store. Note: the Deel API does not return previous hiring status; it is tracked client-side from earlier polls. |
| TimeOffTrigger |
io.kestra.plugin.deel.timeoff.TimeOffTrigger |
Poll the profile time-off endpoint and trigger on requested and approved time-off records. Uses cursor pagination (page_size/next). State is persisted in the KV store. |
Webhook Trigger
| Trigger |
Fully Qualified Name |
Description |
| WebhookTrigger |
io.kestra.plugin.deel.webhooks.WebhookTrigger |
Accept incoming Deel webhook requests and verify HMAC-SHA256 signatures. Each request is verified using HMAC-SHA256 over the concatenation of the POST method and raw request body, using the webhook signing key. Requests with missing or invalid signatures are rejected without triggering a flow. Configure using secretSigningKey and optional events filter. |
Configuration Properties Reference
Common across tasks and triggers
apiToken (Property<String>, required): Deel API authentication token.
Task-specific properties
| Property |
Task |
Type |
Group |
Description |
limit |
Various tasks |
Property<Integer> |
filter |
Maximum records per page (default: 25, max: 100) |
offset |
Various tasks |
Property<Integer> |
filter |
Pagination offset (default: 0) |
cursor |
Various tasks |
Property<String> |
filter |
Cursor for cursor-based pagination |
contractId |
Documents.List, Invoices, Timesheets |
Property<String> |
filter |
Filter by contract ID |
status |
Invoices, Timesheets |
Property<String> |
filter |
Filter by status (e.g., paid, all, or specific statuses) |
dateFrom |
Invoices, Timesheets |
Property<String> |
filter |
Filter records on or after date (YYYY-MM-DD) |
dateTo |
Invoices, Timesheets |
Property<String> |
filter |
Filter records before date (YYYY-MM-DD) |
contractType |
ContractsList |
Property<String> |
filter |
Filter contracts by type (e.g., open, terminated, expired) |
legalEntityId |
Various tasks |
Property<String> |
filter |
Filter by legal entity ID |
teamId |
ContractsList |
Property<String> |
filter |
Filter contracts by team ID |
updatedSince |
ContractsList, PeopleList |
Property<String> |
filter |
Filter by last updated timestamp |
hiringStatus |
PeopleList |
Property<String> |
filter |
Filter people by hiring status |
hiringType |
PeopleList |
Property<String> |
filter |
Filter by hiring type (employee, contractor, EOR) |
search |
PeopleList |
Property<String> |
filter |
Search people by name, email, or other fields |
contractId |
CostCenters (required) |
Property<String> |
filter |
Legal entity ID whose cost centers to return |
country |
LegalEntitiesList |
Property<String> |
filter |
Filter legal entities by country |
entityType |
LegalEntitiesList |
Property<String> |
filter |
Filter by entity type |
globalPayroll |
LegalEntitiesList |
Property<Boolean> |
filter |
Filter by global payroll flag |
includeArchived |
LegalEntitiesList |
Property<Boolean> |
filter |
Include archived legal entities |
events |
ContractTrigger, InvoiceIssuedTrigger, PersonTrigger, TimeOffTrigger |
Property<List<?>> |
main |
Which events trigger executions |
signedStatuses |
ContractTrigger |
Property<List<String>> |
main |
Contract statuses treated as signed (default: ["in_progress"]) |
terminatedStatuses |
ContractTrigger |
Property<List<String>> |
main |
Contract statuses treated as terminated (default: ["terminated", "cancelled", "user_cancelled"]) |
interval |
Polling triggers |
Duration |
main |
Polling interval (default: PT5M) |
hrisProfileId |
TimeOffTrigger (required) |
Property<String> |
main |
Worker HRIS profile ID |
secretSigningKey |
WebhookTrigger (required) |
Property<String>, secret |
connection |
Webhook signing key for HMAC-SHA256 verification |
lookupType |
Lookups.Get (required) |
Property<LookupType> |
main |
Enum: COUNTRIES, CURRENCIES, JOB_TITLES, SENIORITIES |
isEorContract |
Lookups.Get (SENIORITIES) |
Property<Boolean> |
filter |
When true, excludes C-level seniorities |
afterCursor |
Lookups.Get (JOB_TITLES) |
Property<String> |
filter |
Pagination cursor for job titles |
fetchType |
Various tasks |
Property<FetchType> |
execution |
Result handling: FETCH, FETCH_ONE, STORE, NONE (default: FETCH) |
YAML Examples
List approved timesheets for a contract
id: list_contract_timesheets
namespace: company.team
tasks:
- id: list_timesheets
type: io.kestra.plugin.deel.timesheets.List
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
contractId: "contract_abc123"
status: "approved"
dateFrom: "2024-01-01"
dateTo: "2024-12-31"
fetchType: STORE
List invoices with status all and date range
id: list_invoices
namespace: company.team
tasks:
- id: list_invoices
type: io.kestra.plugin.deel.invoices.List
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
status: "all"
issuedFrom: "2024-01-01"
issuedTo: "2024-12-31"
fetchType: FETCH
Get a single contract
id: get_contract
namespace: company.team
tasks:
- id: get_contract
type: io.kestra.plugin.deel.contracts.ContractsGet
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
contractId: "550e8400-e29b-41d4-a716-446655440000"
List people with hiring status filter
id: list_people
namespace: company.team
tasks:
- id: list_people
type: io.kestra.plugin.deel.people.PeopleList
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
hiringStatus: "active"
limit: 50
fetchType: STORE
Look up currencies
id: lookup_currencies
namespace: company.team
tasks:
- id: lookup_currencies
type: io.kestra.plugin.deel.lookups.Get
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
lookupType: CURRENCIES
Poll contracts for events
id: watch_contracts
namespace: company.team
triggers:
- id: watch_contracts
type: io.kestra.plugin.deel.contracts.ContractTrigger
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
interval: PT5M
events:
- created
- signed
- terminated
Webhook trigger for Deel events
id: deel_webhook_events
namespace: company.team
triggers:
- id: deel_webhook
type: io.kestra.plugin.deel.webhooks.WebhookTrigger
apiToken: "{{ secret('DEEL_API_TOKEN') }}"
secretSigningKey: "{{ secret('DEEL_WEBHOOK_SIGNING_KEY') }}"
events:
- contract.created
- contract.signed
Building and Running Locally
Build the shadow JAR: ./gradlew shadowJar
Run with Docker Compose:
docker compose up
- Builds
kestra/kestra: latest and mounts build/libs/ to /app/plugins/
- Kestra UI available at [localhost: 8080](http://localhost: 8080)
Local development notes:
- Mounting a host folder onto
/app/plugins/ replaces the container's plugins directory rather than adding to it. Core plugins compiled into Kestra itself are unaffected, but any additional plugin normally bundled in the base image under /app/plugins/ gets hidden once the mount is in place.
- If a flow depends on another plugin, copy its jar into
build/libs/ too before starting the container.
- JFR repository error on some hosts:
Unable to create JFR repository directory using base location (/tmp). Work around by mounting /tmp as tmpfs or adding -v /tmp:/tmp or --tmpfs /tmp (tracked upstream in kestra-io/kestra#17405).
Links
Stay up to date
New versions released monthly. Star the main repository for notifications and future updates.