Documentation: API


Everee permits clients to use APIs to perform the following common operations in the Everee platform:

  1. Kicking off a worker’s Everee-managed onboarding process
  2. Managing worker data and settings
  3. Submitting one-time payments for processing
  4. Capturing employee timeclock events in real time
  5. Creating, editing, and deleting shifts on an employee’s time card
  6. Retrieving hours-worked and gross pay information for a date range
  7. Retrieving an employee’s pay statements, including gross-to-net pay amounts

Other APIs are available depending on use-case. Please contact Everee for more information.

Kick off worker onboarding

Everee can onboard workers into a company instance through a web-based onboarding sequence. First the worker is sent a secure link from Everee to enter the onboarding sequence. Once they enter the sequence, Everee captures all required information from the worker and then moves them into their personalized, web-based Everee Account app or one of our native mobile apps, where the worker can access their personal and pay information, including pay statements.

When a worker has completed the onboarding sequence, Everee will submit the completed worker record to a webhook specified by the client. See the “Adding workers directly” section for the data structure of a completed worker record.

Kick off onboarding for a contractor

Onboarding a contractor requires minimal data to be sent from a client, since the rest of the contractor’s information will be captured during the Everee-managed onboarding process. Note that this operation will designate the worker as a 1099-issued contractor without a formal employment relationship.

Request line:

POST /api/v2/onboarding/contractor

Query parameters:
none

Request body:

POST /api/v2/embedded/workers/employee
{
          firstName: string [required]
          middleName: string
          lastName: string [required]
          phoneNumber: string [required], 10 digits
          email: string [required], email format
          hireDate: string [required], ISO8601 date
          legalWorkAddress: object [required], with shape: {
              useHomeAddress: boolean [required]
              workLocationId: number, ignored if “useHomeAddress” is “true”
          }
          externalWorkerId: string
          teamId: number
}

Response:

201 Created
{
          workerId: string
          externalWorkerId: string
          firstName: string
          middleName: string
          lastName: string
          phoneNumber: string, 10 digits
          email: string, email format
          hireDate: string, ISO8601 date
          legalWorkAddress: object, with shape: {
                 current: {
                     useHomeAddress: boolean
                     workLocationId: number [optional]
            }
          }
          team: object, with shape: {
          id: number
          name: string
}

Kick off onboarding for an employee

Onboarding an employee requires minimal data to be sent from an client, since the rest of the employee’s  information will be captured during the Everee-managed onboarding process. Note that this operation will designate the worker as a W2-issued employee with a formal employment relationship.

Request line:

POST /api/v2/onboarding/employee

Query parameters:
none

Request body:

{
        firstName: string [required]
        middleName: string
        lastName: string [required]
        phoneNumber: string [required], 10 digits
        email: string [required], email format
        payType: string [required], one of: “HOURLY”, “SALARY”
        payRate: object [required], Money
        title: string
        typicalWeeklyHours: number [required], integer in range 1-40 inclusive
        hireDate: string [required], ISO8601 date
        legalWorkAddress: object [required], with shape: {
                useHomeAddress: boolean [required]
                workLocationId: number, ignored if “useHomeAddress” is “true”
        }
        externalWorkerId: string
        teamId: number
}

Response:
201 Created

{
    workerId: string
    externalWorkerId: string[optional]
    firstName: string
    middleName: string[optional]
    lastName: string
    phoneNumber: string,
    10 digits
    email: string,
    email format
    position: object,
    with shape:
    {
        current:
        {
            payType: string,
            one of: “HOURLY”,
            “SALARY”
            payRate: object,
            Money
            title: string[optional]
        }
    }
    hireDate: string,
    ISO8601 date
    legalWorkAddress: object,
    with shape:
    {
        current:
        {
            useHomeAddress: boolean
            workLocationId: number[optional]
        }
    }
    team: object,
    with shape:
    {
        id: number
        name: string
    }
}

Managing worker data

Add a complete contractor record

A client can choose to directly add a complete contractor record. Note that this operation will designate the worker as a 1099-issued contractor without a formal employment relationship.

The client must capture all required worker data in order to submit a complete worker record, so this option is only appropriate if the integrating application assumes the risk and responsibility of capturing potentially sensitive personally-identifiable data about the worker.

Request line:

POST /api/v2/workers/contractor

Query parameters:
none

Request body:

{
        firstName: string [required]
        middleName: string
        lastName: string [required]
        phoneNumber: string [required], 10 digits
        email: string [required], email format
        hireDate: string [required], ISO8601 date
        homeAddress: object [required], with shape: {
                line1: string [required]
                line2: string
                city: string [required]
                state: string [required], 2-letter state abbreviation
                postalCode: string [required], 5-digit ZIP code
        }
        dateOfBirth: string, ISO8601 date [required]
        taxpayerIdentifier: string [required], 10 digits
        bankAccount: object [required], with shape: {
                bankName: string [required]
                accountName: string [required]
                accountType: string [required], one of: “CHECKING”, “SAVINGS”
                routingNumber: string [required], 9 digits
                accountNumber: string [required], digits only
        }
        legalWorkAddress: object [required], with shape: {
                useHomeAddress: boolean [required]
                workLocationId: number, ignored if “useHomeAddress” is “true”
        }
        externalWorkerId: string
        teamId: number
}

Response:

201 Created

[Worker data structure; see Appendix]

Add a complete employee record

Clients can choose to directly add a complete employee record. Note that this operation will designate the worker as a W2-issued employee with a formal employment relationship.

The client must capture all required worker data in order to submit a complete worker record, so this option is only appropriate if the integrating application assumes the risk and responsibility of capturing potentially sensitive personally-identifiable data about the worker.

Request line:

POST /api/v2/workers/employee

Query parameters:
none

Request body:

{
        firstName: string [required]
        middleName: string
        lastName: string [required]
        phoneNumber: string [required], 10 digits
        email: string [required], email format
        payType: string [required], one of: “HOURLY”, “SALARY”
        payRate: object [required], Money
        hireDate: string [required], ISO8601 date
        homeAddress: object [required], with shape: {
                line1: string [required]
                line2: string
                city: string [required]
                state: string [required], 2-letter state abbreviation
                postalCode: string [required], 5-digit ZIP code
        }
        dateOfBirth: string [required], ISO8601 date
        taxpayerIdentifier: string [required], 10 digits
        typicalWeeklyHours: number [required], integer in range 1-40 inclusive
        bankAccount: object [required], with shape: {
                bankName: string [required]
                accountName: string [required]
                accountType: string [required], one of: “CHECKING”, “SAVINGS”
                routingNumber: string [required], 9 digits
                accountNumber: string [required]
        }
        withholdingSettings: object [required], with shape: {
                haveExactlyTwoJobs: boolean [required]
                countOfChildren: number [required]
                countOfOtherDependents: number [required]
                otherIncomeAnnually: object [required], Money
                deductionsAnnually: object [required], Money
                extraWithholdingsMonthly: object [required], Money
                exempt: boolean [required]
                maritalStatus: string [required], one of:
                        “HEAD_OF_HOUSEHOLD”,
                        “MARRIED_FILING_JOINTLY”,
                        “SINGLE_OR_MARRIED_FILING_SEPARATELY”
        }
        paySchedule: string [required], one of: “DAILY”, “WEEKLY”, “DEFAULT”
        legalWorkAddress: object [required], with shape: {
                useHomeAddress: boolean [required]
                workLocationId: number, ignored if “useHomeAddress” is “true”
        }
        externalWorkerId: string
        teamId: number
}

For more information on the fields in the withholdingSettings structure, please refer to the 2020 version of Form W-4, found at https://www.irs.gov/forms-pubs/about-form-w-4.

Response:

201 Created

[Worker data structure; see Appendix]

Update a worker’s home address

Clients can update a worker’s home address on behalf of the worker. Changing a worker’s home address requires specifying an “effective date” for the change. Unless they have a specific reason to choose a particular date, clients should generally specify an effective date of the next day in the worker’s local time zone.

Request line:

PUT /api/v2/workers/{workerId}/address

Query parameters:
none

Request body:

{
    line1: string[required]
    line2: string
    city: string[required]
    state: string[required], 2 - letter state abbreviation
    postalCode: string[required], 5 - digit ZIP code
    effectiveDate: string, ISO8601 date[required]
}

Response:

200 OK

[Worker data structure; see Appendix]

Update a worker’s bank account

Clients can update a worker’s bank account information on behalf of the worker. Changing a worker’s bank account will cause all payments that haven’t yet been approved for payment, whether the payments currently exist or not, to be deposited to the new account.

Request line:

PUT /api/v2/workers/{workerId}/bank-accounts/default

Query parameters:
none

Request body:

{
    bankName: string[required]
    accountName: string[required]
    accountType: string[required], one of: “CHECKING”, “SAVINGS”
    routingNumber: string[required], 9 digits
    accountNumber: string[required]
}

Response:

200 OK

[Worker data structure; see Appendix]

Delete a worker in onboarding

A worker can be deleted while still in the onboarding process. Deleting a worker isn’t possible if payments or timecard records have already been added. If a worker can’t be deleted for one of these reasons, the request will fail with an error.

Request line:

DELETE /api/v2/workers/{workerId}

Query parameters:
none

Request body:
none

Response:

204 No Content

[no response body]

Retrieve a worker

Clients can retrieve the whole worker data structure on demand.

Request line:

GET /api/v2/workers/{workerId}

Query parameters:
none

Request body:
none

Response:

200 OK

[Worker data structure; see Appendix]

Retrieve a worker by external ID

Clients can retrieve the whole worker data structure on demand.

Request line:

GET /api/v2/workers/external/{externalWorkerId}

Query parameters:
none

Request body:
none

Response:

200 OK

[Worker data structure; see Appendix]

List workers

Clients can retrieve a list of worker data structures on demand.

Request line:

GET /api/v2/workers

Query parameters:

  • worker-id: string [repeatable]
  • external-worker-id: string [repeatable]
  • search-term: string
  • page: number [default: 0]
  • size: number [default: 20]

Request body:
none

Response:

200 OK

{
    items: [
        [Worker data structure;see Appendix]
        ...
    ],
    ...pagination fields...
}

Payment submission

Creating a batch of one-time payments

Processing one-time payments to workers involves creating a batch of payments, which will then be calculated based on the settings specified in the batch and the payments themselves.

Request line:

POST /api/v2/payments/requests

Request parameters:
none

Request body:

{
    title: string[required]
    type: string[required],
    one of: [see Earning Types]
    paymentTimeframe: string[required],
    always“ ASAP”
    paymentMethod: string[required],
    always“ DIRECT_DEPOSIT”
    recipients: array of objects[required],
    with shape:
    {
        amount: object[required],
        Money
        workerId: string[required]
        taxCalcMethod: string[required],
        one of: [see Tax Methods]
        note: string
    }
}

Response:

201 Created
{
    items: [
        {
            id: number
            title: string
            type: string,
            one of: [Earning Types]
            paymentTimeframe: string,
            always“ ASAP”
            paymentMethod: string,
            always“ DIRECT_DEPOSIT”
            paymentCount: number
            totalGrossAmount: Money
        },
        ...
    ],
    ...pagination fields...
}

Retrieving the settings for a one-time payment batch

Retrieve the settings for an existing batch of one-time payments to perform reconciliation tasks.

Request line:

GET /api/v2/payments/requests/{requestId}

Request parameters:
none

Request body:
none

Response:


200 OK
{
    items: [
        {
            id: number
            title: string
            type: string,
            one of: [Earning Types]
            paymentTimeframe: string,
            always“ ASAP”
            paymentMethod: string,
            always“ DIRECT_DEPOSIT”
        },
        ...
    ],
    ...pagination fields...
}

Retrieving one-time payments

Retrieve the list of one-time payments within a specific batch or associated with a specific worker.

Request line:

GET /api/v2/payments/requests/recipient

Request parameters:

  • id: number [repeatable]
  • worker-id: string [repeatable]
  • external-worker-id: string [repeatable]
  • request-id: string [repeatable]
  • page: number [default: 0]
  • size: number [default: 20]

Request body:
none

Response:

200 OK
{
    items: [
        {
            amount: object,
            Money
            paymentId: number
            paymentStatus: string,
            one of: [Payment Statuses]
            payDate: string,
            ISO8601 date
            workerId: string
            externalWorkerId: string
            paymentRequestId: number
            taxCalcMethod: string,
            Tax Method
            note: string
        },
        ...
    ],
    ...pagination fields...
}

Employee time cards

Listing shifts on an employee’s time card

Adding a shift to an employee’s time card records hours worked. A “shift” refers to any part of a whole scheduled work period—if an employee works a scheduled work period in two parts, like one part before a lunch break and another part after, these parts would be recorded as two shifts.

Request line:

GET /api/v2/labor/timesheet/worked-shifts

Request parameters:

  • worker-id: string [repeatable]
  • external-worker-id: string [repeatable]
  • starts-at-or-after: string, ISO8601 timestamp with zone
  • starts-before: string, ISO8601 timestamp with zone
  • ends-at-or-before: string, ISO8601 timestamp with zone
  • page: number [default: 0]
  • size: number [default: 20]

Request body:
none

Response:

200 OK
{
    items: [
        [WorkedShift data structure;see Appendix]
        ...
    ],
    ...pagination fields...
}

Add a shift to an employee’s time card

Adding a shift to an employee’s time card records hours worked. A “shift” refers to any part of a whole scheduled work period—if an employee works a scheduled work period in two parts, like one part before a lunch break and another part after, these parts would be recorded as two shifts.

Request line:

POST /api/v2/labor/timesheet/worked-shifts/epoch

Request parameters:
none

Request body:

{
    workerId: string[required if‘ externalWorkerId’ is not specified]
    externalWorkerId: string[required if‘ workerId’ is not specified]
    shiftStartEpochSeconds: number, UNIX epoch seconds[required]
    shiftEndEpochSeconds: number, UNIX epoch seconds
    overrideHourlyPayRate: string, Money
}

Response:

201 Created

[WorkedShift data structure; see Appendix]

Update a shift on an employee’s time card

Adding a shift to an employee’s time card records hours worked. A “shift” refers to any part of a whole scheduled work period—if an employee works a scheduled work period in two parts, like one part before a lunch break and another part after, these parts would be recorded as two shifts.

Request line:

PUT /api/v2/labor/timesheet/worked-shifts/epoch/{workedShiftId}

Query parameters:
none

Request body:

{
    shiftStartEpochSeconds: number, UNIX epoch seconds[required]
    shiftEndEpochSeconds: number, UNIX epoch seconds
    overrideHourlyPayRate: string, Money
}

Response:

200 OK

[WorkedShift data structure; see Appendix]

Delete a shift on an employee’s time card

If a shift is recorded incorrectly, it can be deleted. Shifts that have already been included as part of a payroll payment can no longer be deleted, and attempting to do so will return an HTTP code 400.

Request line:

DELETE /api/v2/labor/timesheet/worked-shifts/{workedShiftId}

Query parameters:
none

Request body:
none

Response:

204 No Content

Pay history and statements

Retrieve a worker’s pay history

Clients can retrieve a list of payments that have been paid-out to workers. Items returned from these endpoints represent payments that have been successfully processed for deposit.

Request line:

GET /api/v2/workers/payment-history

Query parameters:

  • worker-id: string [required if ‘external-worker-id’ is not provided]
  • external-worker-id: string [required if ‘worker-id’ is not provided]
  • page: number [default: 0]
  • size: number [default: 20]

Request body:
none

Response:

200 OK
{
    items: [
        [PayHistoryItem data structure;see Appendix]
        ...
    ],
    ...pagination fields...
}

Retrieve a pay history item by ID

Clients can retrieve a single payment from a worker’s pay history by “payment ID” if it has been successfully processed for deposit; attempting to request a payment by ID before it has been processed for deposit will result in an HTTP status 404.

Request line:

GET /api/v2/workers/payment-history/{paymentId}

Query parameters:
none

Request body:
none

Response:

200 OK

[PayHistoryItem data structure; see Appendix]

Retrieve a list of available pay statements

Workers receive one pay statement per traditional pay period. A pay statement becomes available to a worker once a payment is processed for that worker within the company’s current default pay cycle. Pay statements are available for both contractors and employees.

Request line:

GET /api/v2/pay-stubs

Query parameters:

  • worker-id: string [required if ‘external-worker-id’ is not provided]
  • external-worker-id: string [required if ‘worker-id’ is not provided]
  • start-date: string, ISO8601 date
  • end-date: string, ISO8601 date
  • page: number [default: 0]
  • size: number [default: 20]

Request body:
none

Response:

200 OK
{
    items: [
    {
        id: number
        workerId: string
        externalWorkerId: string
        date: string, ISO8601 date
        payPeriodStartDate: string, ISO8601 date
        payPeriodEndDate: string, ISO8601 date
        grossEarnings: object, Money
        ytdGrossEarnings: object, Money
        totalTaxesEe: object, Money
        preTaxDeductions: object, Money
        ytdPreTaxDeductions: object, Money
        postTaxDeductions: object, Money
        ytdPostTaxDeductions: object, Money
        deferredCompensation: object, Money
        ytdDeferredCompensation: object, Money
        netEarnings: object, Money
        ytdNetEarnings: object, Money
    }, ],
    ...pagination fields...
}

Retrieve a pay statement

Pay statements are generated as PDF files on-demand and stored to a CDN for fast retrieval. API consumers must access the returned URL to download the pay statement. The download URL is signed with an expiring access token and remains valid until the time indicated by the “expiresAt” value is reached. Once that time is reached, accessing the URL will return an error and the pay statement should be requested again if needed. The URL is valid for at least 10 minutes.

Request line:

GET /api/v2/pay-stubs/download-link

Query parameters:

  • worker-id: string [required if ‘external-worker-id’ is not provided]
  • external-worker-id: string [required if ‘worker-id’ is not provided]
  • date: string, ISO8601 date [required]

Request body:
none

Response:

200 OK
{
    url: string, URL
    expiresAt: number,
    epoch timestamp
}

Reference

Workers

Everee represents workers with a composite data structure that includes relevant details of all common worker-linked data, like demographics, position and compensation, tax withholding settings, and so on.

Note that depending on the worker’s configuration and whether the worker details have been partly or fully recorded, some sections of the data structure may be missing.

{
        workerId: string
        externalWorkerId: string
        firstName: string
        middleName: string
        lastName: string
        phoneNumber: string, 10 digits
        email: string, email format
        onboardingComplete: boolean
        position: object, with shape: {
                current: {
                      payType: string, one of: “HOURLY”, “SALARY”
                      payRate: object, Money
                      title: string [optional]
                      }
                }
        hireDate: string, ISO8601 date
        homeAddress: object, with shape: {
                current: {
                      line1: string
                      line2: string
                      city: string
                      state: string, 2-letter state abbreviation
                      postalCode: string, 5-digit ZIP code
                }
        }
        dateOfBirth: string, ISO8601 date
        taxpayerIdentifier: string, 10 digits
        typicalWeeklyHours: number, integer in range 1-40 inclusive
        bankAccounts: array of objects, with shape: {
                bankName: string
                accountName: string
                accountType: string, one of: “CHECKING”, “SAVINGS”
                routingNumber: string, 9 digits
                accountNumberLast4: string
        }
        withholdingSettings: object, with shape: {
                current: {
                      haveExactlyTwoJobs: boolean
                      countOfChildren: number
                      countOfOtherDependents: number
                      otherIncomeAnnually: object, Money
                      deductionsAnnually: object, Money
                      extraWithholdingsMonthly: object, Money
                      exempt: boolean
                      maritalStatus: string, one of:
                      “HEAD_OF_HOUSEHOLD”,
                      “MARRIED_FILING_JOINTLY”,
                      “SINGLE_OR_MARRIED_FILING_SEPARATELY”
                }
        }
        legalWorkAddress: object, with shape: {
                current: {
                      useHomeAddress: boolean
                      name: string
                      workLocationId: number [optional]
                      address: {
                            line1: string
                            line2: string
                            city: string
                            state: string, 2-letter state abbreviation
                            postalCode: string, 5-digit ZIP code
                      }
                  }
        }
        team: object, with shape: {
              id: number
              name: string
        }
}

WorkedShift

The WorkedShift data structure represents a shift recorded on a worker’s timesheet. Depending on the worker’s configuration, the hours recorded under a WorkedShift may be processed for deposit via a payment record. If a WorkedShift is attached to a payment record for deposit, the payableDetails.paymentId property will contain that payment’s ID. Note that if the payableDetails.paid property is true, the payment record itself will be available to request via the Payment History API.

{
        workerId: string
        externalWorkerId: string
        workedShiftId: number
        shiftStartAt: {
              effectivePunchAt: string, ISO8601 datetime with time zone
        }
        shiftEndAt: {
              effectivePunchAt: string, ISO8601 datetime with time zone
        }
        payableDetails: {
              totalPayableAmount: string, Money
              paid: boolean
              paymentId: number [optional]
        }
        shiftDurations: {
              shiftDuration: string, ISO8601 duration
              regularTimeWorked: {
                  totalDuration: string, ISO8601 duration
                  totalPayableAmount: string, Money
              }
              overtimeWorked: {
                  totalDuration: string, ISO8601 duration
                  totalPayableAmount: string, Money
              }
              doubleTimeWorked: {
                  totalDuration: string, ISO8601 duration
                  totalPayableAmount: string, Money
              }
        }
} 

PaymentHistoryItem

A PaymentHistoryItem represents a single payment to a worker that has been successfully processed for deposit. The payStubDate property can be used to request a pay statement via the Pay Stubs API.

{
        workerId: string
        externalWorkerId: string
        workerDisplayName: string
        paymentId: number
        payStubDate: string, ISO8601 date
        payDate: string, ISO8601 date
        grossEarnings: Money
        totalTaxesWithheld: Money
        preTaxDeductions: Money
        postTaxDeductions: Money
        deferredCompensation: Money
        netEarnings: Money
        payableNotes: array of strings
        taxesWithheld: array of objects, with shape: {
              amount: Money
              amountYearToDate: Money
              name: string
        }
        deductions: array of objects, with shape: {
              amount: Money
              amountYearToDate: Money
              name: string
        }
        deposits: array of objects, with shape: {
              destinationLabel: string
              amount: Money
        }
}

Money

Monetary amounts are fixed-point values, with the decimal string pattern d+.dd, and must be associated with a currency to be meaningful. These values are transmitted through our APIs as structures with the following shape:

{
        amount: string, decimal number with format “d+.dd”
        currency: string, ISO4217 currency code
}

Earning types

The following earning types are supported:

"BONUS": supplemental wages
"COMMISSION": supplemental wages
"CONTRACTOR": not wages
"HOLIDAY": regular wages
"PER_DIEM": supplemental wages
"REGULAR_HOURLY": regular wages
"REIMBURSEMENT": not wages
"TIPS": regular wages

Payment statuses

Payments may be in one of the following statuses:

"PROCESSING": payment is calculating or waiting to be calculated
"PENDING_APPROVAL": payment is ready to be approved for deposit
"PENDING_PAYMENT": payment is approved and waiting to be submitted for deposit
"PAID": payment has been submitted for deposit and/or successfully deposited
"ERROR": payment has experienced an error during calculation or deposit

Tax methods

The following tax methods are supported:

"FLAT": flat tax of 22% on the first $1M and 37% on the excess (for tax year 2021)
"AGGREGATION":aggregated tax rate based on prior or current payroll wages

When submitting payments to contractors, no tax method is used, since no taxes are calculated for these payments. Specify the FLAT method in these cases

Pagination

Endpoints that return lists of data are paginated, and accept two parameters to control the windowing of a specific request:

size: number – the page size to apply. Analogous to SQL LIMIT {size}
page: number – the page index to apply. Analogous to SQL OFFSET {size * page}

Paginated responses include pagination metadata to allow clients to drive pagination correctly. These responses have the following shape:

{
      items: array, requested datatype
      pageNumber: number
      pageSize: number
      totalItems: number
      totalPages: number
}

API Authentication

All endpoints require valid authentication to allow access. An API token can be generated in the Integrations section of the Everee web app. The API token should be included in all requests using a header with the following shape

authorization: basic base64_encode(api_token)

Note: an API token is tied to one company instance and grants access only to that instance while active. Clients integrating with multiple company instances must select the appropriate API token to perform operations in each instance.

Once an API token has been deleted or rotated, and it has expired, it will no longer be valid for authentication and any requests using that token will respond with an HTTP 401 error.

Webhook events

The Everee platform supports webhook notifications for the following event types:

worker.onboarding-completed
worker.updated-payment-method
worker.updated-home-address
worker.updated-payroll-frequency
worker.updated-personal-info
worker.updated-withholding-settings

Note: additional event types are available and can be configured on a per-client basis.

During account configuration, clients can provide the following information to enable webhook events from Everee to their systems:

  1. Target URL: an HTTPS URL that will receive webhook payloads
  2. Signing key: a secret key string used to sign webhook payloads, so clients can verify authenticity of received webhook event

When a webhook event is sent to a client’s target URL, the payload has the following structure:

{
        id: string, unique ID of the event
        companyId: number, unique ID of the client Company instance in Everee
        type: string, event type name
        timestamp: number, epoch timestamp of the event’s original occurrence
        data: object, with shape: {
              object: object, with shape: {
                    [relevant object structure]
              }
        }
        version: string, version of the payload structure; currently always "1"
}

Errors, retries, and idempotency

When implementing a webhook endpoint, clients must stick to the following rules to avoid failed event delivery:

  1. The endpoint must respond to the webhook event with an HTTP status code in the range 2xx. Any other status code, including redirecting with a 3xx code, is a failure.
  2. The endpoint must respond quickly to the webhook event request. Make sure to respond with a 2xx code before triggering any asynchronous work.

When a failure occurs, Everee tries to deliver the event multiple times over the next few days. Retries are delayed with exponential backoff, which causes each retry attempt to wait longer than the last attempt.

After a few days of failures, Everee will stop trying to deliver the webhook event, and information about the relevant object will need to be fetched from the Everee API on demand.

It’s possible to occasionally receive the same webhook event more than once. Clients should ensure their webhook endpoint is idempotent by keeping track of the unique ID of the event. If a duplicate event is received, clients should skip processing it and respond with an HTTP status code 2xx.

Authenticating a webhook event

In order for clients to authenticate that a webhook event was sent from Everee and can be trusted, HTTP headers like the following examples are included on each request:

x-everee-webhook-timestamp: 1617756644
x-everee-webhook-signature: v1=939783e540[...],v1=77afec359d7[...]

The x-everee-webhook-signature header may contain multiple signatures if there are multiple webhook signing keys active for a client account. Each signature is paired with a version identifier; currently the only valid version is v1.

To verify that a webhook event is authentic:
  1. Extract the versioned signature strings by splitting the value of the x-everee-webhook-signature header on the comma character (Unicode U+002C) and discarding any strings that don’t start with the version v1.
  2. Split each versioned signature string on the equals character (Unicode U+003D), discard the first value (the version part, currently always v1), and retain the second value, the signature.
  3. Create the “message” by concatenating the timestamp in the x-everee-webhook-timestamp header, a period character (Unicode U+002E), and the raw JSON payload of the webhook event (the HTTP body).
  4. Compute an HMAC signature of that message using the SHA-256 cryptographic hash function and the webhook signing key provided to Everee. Ensure the signature is hex-encoded.
  5. Compare the computed signature with each signature extracted from the header. If any signature matches, the webhook is authentic.

Or in psuedocode

authentic = false
message = concat(timestamp, “.”, json_body)
computed_signature = hmac_sha256(secret_key, message)
for each signature in extracted_signatures:
    authentic = authentic || secure_compare(signature, computed_signature)

Securing a webhook endpoint

To prevent timing attacks, always use a comparison function that operates in constant time when comparing signatures or hashes. This helps prevent an attacker from determining the secret signing key.

To prevent replay attacks:

  1. Ensure your server’s local time is synchronized with NTP.
  2. Verify that the timestamp in the x-everee-webhook-timestamp header is within your tolerance threshold by comparing it to the current time. We recommend a threshold of 5 minutes or less; if the current time is at least 5 minutes later than the received timestamp, you should reject the webhook event by responding with something other than an HTTP status code 2xx.

Everee only sends webhook events to endpoints secured with HTTPS.