Time Integrations
Where Time Tracking shows up across the rest of Workestra, plus webhooks, AI tools, and the public REST API.
Time Tracking is connective tissue. This page lists every place it surfaces inside other modules, and every external integration point (webhooks, AI, public API).
Where it appears in the UI
Projects
Time tracking is deeply integrated with Projects:
| Surface | What you can do |
|---|---|
| Task detail page | Embedded <TimeEntryQuickAdd> — log time directly from a task, no context-switch. Below it, a list of all time entries on that task. |
| Task list | (Read-only) total hours logged per task in the table view. |
| Project detail / Budget tab | Project actuals (sum of approved hours) shown alongside the budget. Estimate-vs-actual delta computed from tasks.estimate_hours. |
| Reports → Estimate vs. Actual | Per-project bar chart fed from the time rollup service. |
Screenshot needed — task detail right rail with the QuickAdd form and entry list below
Support
| Surface | What you can do |
|---|---|
| Ticket detail page | Embedded <TimeEntryQuickAdd> for the ticket. |
| Ticket list / Reports | Total hours per ticket can roll up to a customer's billable total (when ticket is linked to a billable contract / project). |
CRM
| Surface | What you can do |
|---|---|
| Deal detail | Log time on a deal — useful for tracking pre-sales effort, demo prep, RFP responses. |
| Contact detail | (read-only) Total billable hours for this contact across all entities. |
Recruiting
| Surface | What you can do |
|---|---|
| Candidate detail | Right-rail <TimeOnRecord> widget — total hours, recent entries, time-to-hire breakdown by stage, Start timer button. |
Scheduling / Calendar
| Surface | What you can do |
|---|---|
| Calendar event detail | "Convert to time entry" header action turns a meeting into a tracked entry. |
| Calendar import bridge | Connected Google / M365 events appear as ghost blocks in the timer's Calendar view. See Calendar Import. |
Inbox
| Surface | What you can do |
|---|---|
| Conversation detail | "Log time on this conversation" button — pre-fills the description from the thread subject. |
Finance
| Surface | What you can do |
|---|---|
| Invoice form | The "Pull from time" button. See Rates & Billing. |
| Recurring invoices | (v1.5) Auto-pull time on a schedule — not in v1. |
People (when subscribed)
| Surface | What you can do |
|---|---|
| Employee profile / Utilization tab | Read-only view of utilization (lights up only when the People module is subscribed). |
| Soft dependency | The People module's employees.weekly_hours is used as the utilization denominator. Without People, falls back to time_tracking_settings.default_weekly_hours. |
Webhooks
Time tracking emits events through the standard webhook factory. Configure them at /time/settings/integrations (Pro — requires Projects or Finance).
Available events
| Event | Fired when |
|---|---|
time_entry.created | A new entry is logged (timer or manual) |
time_entry.updated | An entry is edited |
time_entry.deleted | An entry is deleted |
time_entry.invoiced | An entry is consumed by the invoice bridge |
timer.started | Someone starts a timer |
timer.stopped | Someone stops a timer |
timesheet.submitted | A timesheet transitions draft → submitted |
timesheet.approved | A timesheet transitions submitted → approved |
timesheet.rejected | A timesheet transitions submitted → rejected |
Payload shape
{
"id": "evt_abc123",
"event": "timesheet.approved",
"workspace_id": "ws_xyz",
"data": {
"id": "ts_abc",
"user_id": "u_def",
"period_start": "2026-04-22",
"period_end": "2026-04-28",
"status": "approved",
"total_hours": 38.5,
"billable_hours": 32.0,
"approved_at": "2026-04-29T10:23:00Z",
"approved_by": "u_mgr"
},
"timestamp": "2026-04-29T10:23:00Z"
}Delivery semantics
- HMAC-SHA256 signed with
<timestamp>.<body>— headerX-Workestra-Signature: sha256=<hex> - Retries: 1 min → 5 min → 30 min → fail (4 attempts total)
- Status code handling: 2xx delivered, 4xx (except 429) permanent fail, 429/5xx retry
- Per-request timeout: 10s
- Delivery log: every attempt is logged with status, response code, and response body — visible in the UI
Test webhook
The settings UI includes a Test button per webhook. It fires a synthetic webhook.test event so you can verify your endpoint receives + signs correctly without waiting for a real event.
Programmatic subscription (Zapier-friendly)
For automation tools that need to register webhooks without going through the UI:
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/time/webhooks | Create a webhook subscription. Body: { url, events[], secret?, enabled? }. Returns { id, url, events, enabled, created_at, secret } — the secret is returned only once on create; store it for HMAC verification. |
DELETE | /api/v1/time/webhooks/{id} | Delete a subscription. |
Both endpoints use the standard v1 bearer-token auth. This pair is the surface a Zapier app (or any external automation) consumes — the user pastes their API token into Zapier's connection screen, Zapier POSTs to subscribe, receives the secret, and uses it to verify every payload signature.
AI tools
Ten time tools are auto-discovered by the AI chat (/api/ai/chat) and the MCP server.
| Tool | Type | What it does |
|---|---|---|
get_active_timer | read | Returns the running entry (or null) |
get_my_timesheet | read | Current week (or specified period_start) |
get_utilization | read | Stats for a user / project / range — Pro |
log_time_entry | write | Manual entry — needs entity_type, hours, log_date |
start_timer | write | Starts a timer; auto-stops any running timer for the user |
stop_timer | write | Stops the user's active timer |
submit_timesheet | write | Submit a draft timesheet for the given week |
approve_timesheet | write | Approve a submitted timesheet |
reject_timesheet | write | Reject with a reason |
convert_time_to_invoice_lines | write | Pull approved billable hours onto an invoice — Pro |
Write tools always return requires_confirmation: true with the prepared payload. The user sees a confirmation card in the chat UI, approves, and the mutation runs client-side (so the user's auth + permissions apply, not the AI's).
Examples:
- "How many billable hours did I log this week?" →
get_my_timesheet→ answer + breakdown - "Start a timer on the Acme onboarding ticket" →
start_timer(write — confirms first) - "Pull last month's billable hours for Acme onto invoice INV-2026-042" →
convert_time_to_invoice_lines(write — preview + confirm)
Public REST API
All endpoints under /api/v1/. Bearer-auth via Supabase session token. Standard v1 response envelope: { data, pagination? } for success, { error: { code, message } } for errors. X-API-Version: v1 header on every response.
Time entries
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/time-entries | List with filters: entity_type, entity_id, project_id, user_id, status, from, to, billable, page, limit |
POST | /api/v1/time-entries | Create a new entry |
GET | /api/v1/time-entries/{id} | Read one entry |
PATCH | /api/v1/time-entries/{id} | Update fields |
DELETE | /api/v1/time-entries/{id} | Delete (soft — sets invoice_line_id to null on cascade) |
Timesheets
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/timesheets | List with filters: user_id, status, period_start_from, period_start_to |
GET | /api/v1/timesheets/{id} | Read one — includes joined entries |
PATCH | /api/v1/timesheets/{id} | Update fields |
POST | /api/v1/timesheets/{id}/submit | Transition draft → submitted |
POST | /api/v1/timesheets/{id}/approve | Transition submitted → approved (requires time:approve) |
POST | /api/v1/timesheets/{id}/reject | Transition submitted → rejected (requires time:approve); body: { reason } |
State-transition endpoints enforce the same status-precondition guards as the UI — calling approve on a draft timesheet returns a 4xx with a clear error.
Timer
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/time/timer/active | Returns the running timer for the calling user, or null |
POST | /api/v1/time/timer/start | Body: { entity_type, entity_id?, project_id?, description?, billable? }. Atomically stops any prior timer first. |
POST | /api/v1/time/timer/stop | Stops the running timer. No body required. |
These three are the surface the Browser Extension consumes.
Rates
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/time/rates | List rate overrides for the workspace |
POST | /api/v1/time/rates | Upsert (create on no id, update on id) |
DELETE | /api/v1/time/rates/{id} | Delete an override |
Settings + reports
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/time/settings | Read workspace time-tracking settings |
PATCH | /api/v1/time/settings | Partial update |
GET | /api/v1/time/webhooks | List configured webhooks |
GET | /api/v1/time/reports/summary?from=&to= | Workspace-wide totals over a date range — used by the browser extension and external dashboards |
Crons
Four daily crons drive automated time-tracking behavior. All on Vercel Hobby's daily-only schedule.
| Cron | UTC time | What it does |
|---|---|---|
deliver-time-webhooks | 04:30 | Drain pending time_webhook_deliveries, retry failed |
time-calendar-refresh | 04:45 | Pull last-7-days events from connected Google / M365 calendars into time_calendar_imports |
time-running-timer-nudges | 06:30 | Notify (free) or auto-stop (Pro) timers exceeding runaway_timer_hours |
time-auto-submit-timesheets (Pro) | 05:30 | If auto_submit_dow matches today, transition draft → submitted |
time-weekly-digest (Pro) | 07:30 | Email each user their week-so-far summary |
time-budget-alerts | 16:45 | Evaluate every project with a time_budget_hours; notify admins (and email when over 100 %) |
time-tracking-reminders | 17:25 | Daily / weekly reminders for users below reminder_min_*_hours |
time-scheduled-reports | 06:20 | Fire saved-view email schedules due today (daily always, weekly on Mondays, monthly on the 1st) |
Each cron is CRON_SECRET-guarded.
Audit log
Every state-changing operation writes an entry to audit_log:
| Action | Entity type | Fired by |
|---|---|---|
time_entry.created | time_entry | Manual + timer + AI tool + API |
time_entry.updated | time_entry | Edits |
time_entry.deleted | time_entry | Deletes |
timer.started | time_entry | Timer start |
timer.stopped | time_entry | Timer stop |
timesheet.submitted | timesheet | Submit action |
timesheet.approved | timesheet | Approve action |
timesheet.rejected | timesheet | Reject action |
time_entry.rate_override.upserted | time_rate_override | Rate edits |
invoice.lines_filled_from_time | invoice | Invoice bridge convert |
time_webhook.upserted | time_webhook | Webhook config saved |
time_webhook.deleted | time_webhook | Webhook config deleted |
All audit rows include workspace_id, user_id, changes (small jsonb diff), and ip_address / user_agent when available.
Next steps
- Settings — wire up webhooks and crons
- Rates & Billing — the invoice bridge in detail