Skip to main content
Webhooks let your application receive HTTP POST notifications whenever something changes in Everhour — a task is created, a timer is stopped, an invoice is updated. Instead of polling the API, you register a URL and Everhour delivers events to it as they happen.

How it works

  1. You register a targetUrl via POST /hooks.
  2. Everhour performs a handshake to verify the endpoint is reachable.
  3. When a subscribed event fires, Everhour POSTs the event payload to your URL.

Getting started with webhooks

1

Register a webhook

Send a POST /hooks request with your targetUrl and the list of events you want to receive.Your endpoint must be publicly reachable. Everhour will immediately perform a handshake to confirm it is live.A successful registration returns 201 Created. If the handshake fails, you will receive an Invalid callback error. If you have already registered an active webhook for the same URL, you will receive 409 Conflict. If a previous webhook for that URL was disabled, it will be restored instead.
2

Handle the handshake

When you create (or update) a webhook, Everhour sends a POST request to your targetUrl with the following headers:
User-Agent: Everhour (everhour.com)
X-Hook-Secret: <value>
Your endpoint must respond with any 2xx status code. No specific response body is required. If you do not respond with 2xx, the subscription request is rejected.The X-Hook-Secret value is sent once during the handshake only. It is not repeated on event deliveries.For automated testing or CI environments where you cannot serve a live endpoint, include the X-Skip-Handshake: true header on your subscription request to bypass the handshake check.
3

Receive events

After a successful handshake, Everhour will POST event payloads to your URL whenever a subscribed event fires. Events are delivered asynchronously via an internal message queue.Respond with a 2xx status to acknowledge receipt. Sustained delivery failures may cause Everhour to set the webhook active=false. To reactivate a disabled webhook, send a PUT /hooks/{id} request with the desired events list.

Registering a webhook

curl -X POST https://api.everhour.com/hooks \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targetUrl": "https://example.com/everhour-events",
    "events": ["api:task:created", "api:timer:started"]
  }'

Managing webhooks

MethodPathDescription
GET/hooksList all webhooks for the authenticated user
GET/hooks/{id}Get a single webhook
POST/hooksCreate a webhook (returns 201 Created)
PUT/hooks/{id}Update subscribed events or project filter
DELETE/hooks/{id}Delete a webhook (returns 204 No Content)
Webhooks are scoped to the authenticated user — each API key sees only the webhooks it created. A user cannot register the same targetUrl more than once. When updating a webhook with PUT /hooks/{id}, the targetUrl field is ignored. You can change only the subscribed events and the project filter. A handshake is performed again on update to confirm the endpoint is still reachable.

Project-scoped webhooks

A webhook can be scoped to a single project by including a project field on creation. Project-scoped webhooks receive events only for that project.
Project-scoped webhooks cannot subscribe to api:project:created, api:client:*, or api:invoice:* events. Those events are account-level and are only available on unscoped webhooks.

Event types

At least one event must be selected when creating a webhook.
EventTriggered when
api:project:createdA project is created
api:project:updatedA project’s details are updated
api:project:removedA project is deleted
api:task:createdA task is created
api:task:updatedA task’s details are updated
api:task:removedA task is deleted
api:task:recoveredA previously deleted task is restored
api:timer:startedA timer is started
api:timer:stoppedA running timer is stopped
api:time:updatedA time record is created or modified
api:section:createdA section is created
api:section:updatedA section’s details are updated
api:section:removedA section is deleted
api:section:recoveredA previously deleted section is restored
api:client:createdA client is created
api:client:updatedA client’s details are updated
api:estimate:updatedA task estimate is changed
api:invoice:createdAn invoice is created
api:invoice:updatedAn invoice is updated
api:invoice:deletedAn invoice is deleted

Event payload structure

Everhour sends a POST request to your targetUrl with a JSON body in the following shape:
{
  "event": "api:task:created",
  "createdAt": "2026-05-04 12:00:00",
  "data": {
    "id": "gh:123456",
    "data": {
      // full serialized resource object — same shape as the corresponding REST endpoint
    }
  },
  "attributes": {
    "projects": ["ev:1"],
    "users": [123]
  },
  "user": {
    "id": 123,
    "teamId": 456
  }
}
  • event — the event type that fired
  • createdAt — timestamp of the event in YYYY-MM-DD HH:MM:SS format (UTC)
  • data.data — the full resource object, identical in shape to the corresponding REST API response
  • attributes.projects — list of project IDs related to the event
  • attributes.users — list of user IDs related to the event
  • user — the Everhour user whose action triggered the event

Security

Everhour does not sign event delivery payloads. There is no HMAC or signature header on the POST requests sent to your targetUrl. The X-Hook-Secret header is only sent once during the subscription handshake and is not repeated on event deliveries.To secure your endpoint, use TLS (HTTPS) and treat the targetUrl itself as a secret. Anyone who knows the URL can POST arbitrary data to it, so avoid publishing or logging the URL.
The handshake on subscription creation verifies that your endpoint is reachable, but it does not establish an ongoing trust mechanism. Design your endpoint to validate the structure and content of incoming payloads as appropriate for your use case.

Delivery and reliability

  • Deliveries are asynchronous. There is no guaranteed maximum latency between an event firing and your endpoint being called.
  • If your endpoint returns a non-2xx response, Everhour will retry delivery. Sustained failures may cause the webhook to be automatically disabled (active=false).
  • To reactivate a disabled webhook, send a PUT /hooks/{id} request with the desired events list. This triggers a new handshake.
  • Specific retry counts and timeout windows are managed internally and are not configurable via the API.