Everee permits clients to use APIs to perform the following common operations in the Everee platform:
- Kicking off a worker’s Everee-managed onboarding process
- Managing worker data and settings
- Submitting one-time payments for processing
- Capturing employee timeclock events in real time
- Creating, editing, and deleting shifts on an employee’s time card
- Retrieving hours-worked and gross pay information for a date range
- 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 wagesPayment 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 depositTax 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 wagesWhen 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:
- Target URL: an HTTPS URL that will receive webhook payloads
- 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:
- 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.
- 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
.
- 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 versionv1
. - 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. - 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). - 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.
- 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:
- Ensure your server’s local time is synchronized with NTP.
- 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.