Webhooks

Real-time event notifications for WorkSkedge. Subscribe to events and receive instant updates at your endpoints.

WorkSkedge Webhooks

WorkSkedge provides a powerful webhook system that enables seamless integration with your existing tools and workflows. Connect to Microsoft Dynamics 365, QuickBooks, custom applications, and more.

Inbound Webhooks

Send data to WorkSkedge from your systems. Create work orders, update projects, manage employees, and more via HTTP POST requests.

Outbound Webhooks

Receive real-time notifications from WorkSkedge when events occur. Get notified about work order updates, project changes, employee activity, and more.

Getting Started

1

Get Your API Key

Log in to your WorkSkedge dashboard and navigate to Settings → Integrations to generate your API key.

Open Dashboard
2

Choose Your Integration

Decide whether you need to send data to WorkSkedge (Inbound), receive notifications from WorkSkedge (Outbound), or both.

3

Review the Documentation

Use the tabs above to explore Inbound endpoints, Outbound webhook events, and the complete Event Catalog.

4

Test Your Integration

Use the interactive API explorer below to test endpoints with your API key and see live responses.

Interactive API Explorer

Test endpoints live, view request/response schemas, and generate code samples in multiple languages. Enter your API key to try it out.

Loading API documentation...

Webhook Subscription Management

Configure your webhook subscriptions to receive real-time notifications for specific events. WorkSkedge sends HTTP POST requests to your registered endpoints whenever subscribed events occur.

1

Register Endpoint

Add your webhook endpoint URL in Settings → Webhooks. Must be a publicly accessible HTTPS URL.

2

Subscribe to Events

Select which event types you want to receive. You can subscribe to specific events or entire categories.

3

Verify Signature

Implement signature verification in your endpoint to ensure webhooks are authentic.

4

Handle Events

Process incoming webhook payloads and respond with 200 OK within 10 seconds.

Rate Limits

Different rate limits apply depending on whether you're sending data to WorkSkedge or receiving notifications:

  • REST API: 60 requests per minute, 1,000 per hour, 10,000 per day (per API key)
  • Inbound Webhooks: 1,000 requests per hour per API key
  • Outbound Webhooks: Unlimited (WorkSkedge sends to your endpoints)
  • Burst Limit: Up to 50 requests per minute for inbound requests

Rate limit headers are included in all API responses. Contact support if you need higher limits for your integration.

Need Help?

Our team is here to help you integrate WorkSkedge with your systems. Reach out if you have questions or need custom integration support.

Inbound Webhooks

Send data to WorkSkedge from your external systems. Create and update work orders, projects, employees, vendors, and more via simple HTTP POST requests.

Authentication

All inbound webhook requests must include your API key in the X-API-Key header. You can generate your API key from the WorkSkedge dashboard.

Authentication Header
curl -X POST https://app.workskedge.com/webhooks/inbound \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -H "X-Webhook-Signature: your-hmac-signature" \
  -H "X-Webhook-Source: custom" \
  -d '{
    "event_type": "work_order.created",
    "timestamp": "2025-01-15T14:30:00Z",
    "source": "erp_system",
    "data": {
      "project_code": "PRJ-2025-001",
      "phase_name": "Foundation Installation",
      "status": "scheduled",
      "priority": "high"
    }
  }'
Authentication Headers
  • X-API-Key - Your WorkSkedge API key (required)
  • X-Webhook-Timestamp - Unix timestamp when webhook was sent (required)
  • X-Webhook-Signature - HMAC-SHA256 signature of request body (required)
  • X-Webhook-Source - Source system for signature verification (optional). Valid values: custom, square, quickbooks, dynamics365. Defaults to custom if not provided.

Echo Storm Prevention

WorkSkedge automatically prevents infinite webhook loops (echo storms) when integrating bidirectional systems. When you send data to WorkSkedge via inbound webhooks, the system tracks which API key originated each change.

How It Works

The echo prevention mechanism operates automatically in three steps:

  1. Track Origin: When your system sends an inbound webhook (e.g., creating a work order), WorkSkedge records your API key as the originator of that change
  2. Filter Outbound Webhooks: When that change triggers outbound webhook notifications, WorkSkedge automatically excludes your webhook endpoint from receiving the notification
  3. Notify Others: All other systems subscribed to that event type still receive the notification normally
Example Scenario

Your ERP system sends an inbound webhook to create a project in WorkSkedge:

  1. Your ERP sends: POST /webhooks/inbound with event_type: "project.created"
  2. WorkSkedge creates the project and records your API key as the originator
  3. WorkSkedge triggers project.created outbound webhooks to all subscribers
  4. Your ERP is automatically excluded from receiving this outbound webhook
  5. Other integrated systems (e.g., accounting software, CRM) receive the notification normally

Result: Your ERP doesn't receive its own changes back, preventing an infinite loop where it would try to create the same project again.

Key Benefits
  • Automatic Protection: No configuration needed - works out of the box for all API integrations
  • Bidirectional Sync: Safely implement two-way data synchronization between systems
  • No Manual Tracking: You don't need to implement your own loop detection logic
  • Granular Control: Each API key is tracked independently, allowing multiple systems to integrate safely

Available Endpoints

POST
/webhooks/inbound

Work Order Operations

Create, update, or delete work orders in WorkSkedge. Use event types work_order.created, work_order.updated, or work_order.deleted with the appropriate data fields.

CREATE

Create Work Order

Create a new work order in WorkSkedge with complete project details.

Request Body

work-order-create.json
{
  "event_type": "work_order.created",
  "timestamp": "2025-01-15T14:30:00Z",
  "source": "erp_system",
  "data": {
    "project_code": "PRJ-2025-001",
    "phase_name": "Foundation Installation",
    "status": "scheduled",
    "priority": "high",
    "start_date": "2025-02-01",
    "end_date": "2025-02-05",
    "start_time": "08:00",
    "end_time": "17:00",
    "expected_start_date": "2025-02-01",
    "expected_end_date": "2025-02-05",
    "expected_start_time": "08:00",
    "expected_end_time": "17:00",
    "people_required": 5,
    "people_skill_requirements": {
      "Concrete Finisher": 3,
      "Equipment Operator": 2
    },
    "days_hours_required": "5 days, 9 hours per day",
    "performed_by": "internal",
    "site_lead_emp_number": "EMP-1001",
    "scope_of_work": {
      "checklists": [
        {
          "checklist_name": "Site Preparation",
          "items": [
            {"item_name": "Clear and level site", "status": "pending"},
            {"item_name": "Set up formwork", "status": "pending"}
          ]
        },
        {
          "checklist_name": "Pour and Finish",
          "items": [
            {"item_name": "Pour concrete", "status": "pending"},
            {"item_name": "Finish and cure", "status": "pending"}
          ]
        }
      ]
    },
    "equipment": ["Concrete mixer", "Vibrator", "Laser level"],
    "specialty_materials": ["High-strength concrete", "Rebar grade 60"],
    "scheduler_notes": {
      "subtrade_required": "Concrete",
      "scheduling_insights": "Weather-dependent, requires 3-day cure time"
    },
    "custom_fields": {
      "permit_number": "BUILD-2025-001",
      "inspection_required": "yes"
    },
    "display_order": 1,
    "require_daily_report": true,
    "require_completion_report": true
  }
}
Important: Scope of Work Field Names

The webhook accepts the following field names for scope_of_work and automatically converts them to the internal format:

  • checklist_name → stored as title
  • item_name → stored as text
  • status ("pending" or "complete") → stored as completed (boolean)

You can use either format when sending webhooks. The example above shows the input format, which is automatically converted internally.

Response

200 OK
{
  "success": true,
  "message": "Webhook processed successfully",
  "event_id": "evt_1234567890",
  "record_id": "rec_abcdef123456"
}

UPDATE

Update Work Order

Update the Foundation Installation work order status to in-progress and increase crew size due to weather constraints. The work_order_number field is required to identify which work order to update.

Request Body

work-order-update.json
{
  "event_type": "work_order.updated",
  "timestamp": "2025-02-01T08:30:00Z",
  "source": "erp_system",
  "data": {
    "work_order_number": "WO-2025-001",
    "project_code": "PRJ-2025-001",
    "phase_name": "Foundation Installation",
    "status": "in_progress",
    "priority": "urgent",
    "people_required": 7,
    "end_date": "2025-02-06",
    "scheduler_notes": {
      "subtrade_required": "Concrete",
      "scheduling_insights": "Additional crew requested due to weather constraints. Rain expected tomorrow."
    }
  }
}

Response

200 OK
{
  "success": true,
  "message": "Work order updated successfully",
  "event_id": "evt_2345678901",
  "record_id": "rec_abcdef123456"
}

DELETE

Delete Work Order

Delete the Foundation Installation work order. The work_order_number field is required for deletion (or use project_code and phase_name together to identify the work order).

Request Body

work-order-delete.json
{
  "event_type": "work_order.deleted",
  "timestamp": "2025-02-10T16:00:00Z",
  "source": "erp_system",
  "data": {
    "project_code": "PRJ-2025-001",
    "phase_name": "Foundation Installation"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Work order deleted successfully",
  "event_id": "evt_3456789012",
  "record_id": "rec_abcdef123456"
}

Field Aliases Reference

The following field aliases are supported for convenience. Both field names are accepted and automatically mapped:

Alias Field
Maps To
required_headcount
people_required
estimated_hours
days_hours_required
requested_start_date
expected_start_date
requested_end_date
expected_end_date
requested_start_time
expected_start_time
requested_end_time
expected_end_time

Required Fields by Operation Type

Work Order CREATE:
  • project_code - Required
  • phase_name - Required
Work Order UPDATE:
  • work_order_number - Required to identify which work order to update
  • project_code - Optional (if provided, changes the work order's project)
  • phase_name - Optional
Work Order DELETE:
  • work_order_number - Required OR
  • project_code + phase_name - Together can identify the work order
POST
/webhooks/inbound

Project Operations

Create, update, delete, or close projects in WorkSkedge. Use event types project.created, project.updated, project.deleted, or project.closed with the appropriate data fields.

CREATE

Create Project

Create a new project in WorkSkedge with complete project details. The customer_name and location_name fields are required and will automatically create the customer and location if they don't already exist.

Auto-Creation Behavior: When creating a project, if the customer_name doesn't exist, a new customer will be created. If the location_name doesn't exist under that customer, a new location will be created. You can provide optional customer and location fields (like customer_email, location_address_line_1, etc.) which will be used if the customer or location needs to be created. Similarly, contacts in the contacts array will be created and linked to the customer.

Request Body

project-create.json
{
  "event_type": "project.created",
  "timestamp": "2025-01-15T10:00:00Z",
  "source": "crm_system",
  "data": {
    "project_name": "Skyline Tower Construction",
    "project_code": "PRJ-2025-100",
    "description": "15-story mixed-use building with retail and residential units",
    "customer_name": "Skyline Development Corp",
    "location_name": "Broadway Tower Site",
    "customer_type": "Commercial",
    "customer_email": "info@skylinedev.com",
    "customer_phone": "+1-555-0100",
    "customer_website": "https://www.skylinedev.com",
    "customer_status": "Active",
    "customer_notes": "Prefers morning meetings, needs weekly progress updates",
    "location_address_line_1": "789 Broadway",
    "location_city": "New York",
    "location_province": "NY",
    "location_postal_code": "10003",
    "location_country": "USA",
    "location_notes": "Construction site with limited access hours",
    "contacts": [
      {
        "first_name": "Lisa",
        "last_name": "Rodriguez",
        "title": "Project Manager",
        "email": "lisa.r@skylinedev.com",
        "phone": "+1-555-0101",
        "mobile": "+1-555-0201",
        "is_primary": true,
        "contact_tags": ["decision_maker", "primary_contact"]
      },
      {
        "first_name": "Tom",
        "last_name": "Anderson",
        "title": "Site Supervisor",
        "email": "tom.a@skylinedev.com",
        "phone": "+1-555-0102",
        "notes": "On-site daily, prefers text messages for urgent issues"
      }
    ],
    "account_manager_emp_number": "EMP-1001",
    "internal_manager_emp_number": "EMP-2001",
    "priority": "high",
    "color_index": 5,
    "equipment": ["Tower crane", "Concrete pump", "Material hoist"],
    "custom_fields": {
      "building_permit": "NYC-BUILD-2025-5678",
      "insurance_policy": "GLI-2025-9876",
      "contract_value": "50000000"
    },
    "docs": [
      {
        "name": "Architectural Plans.pdf",
        "url": "https://storage.example.com/skyline/arch-plans.pdf"
      },
      {
        "name": "Structural Drawings.pdf",
        "url": "https://storage.example.com/skyline/struct-draw.pdf"
      }
    ]
  }
}

Response

200 OK
{
  "success": true,
  "message": "Webhook processed successfully",
  "event_id": "evt_2345678901",
  "record_id": "rec_project_skyline"
}

UPDATE

Update Project

Update the Skyline Tower project with new contacts and revised contract value after scope expansion. You can also update customer/location information or switch to a different customer/location by providing the customer_name and location_name fields.

Request Body

project-update.json
{
  "event_type": "project.updated",
  "timestamp": "2025-01-20T10:00:00Z",
  "source": "crm_system",
  "data": {
    "project_code": "PRJ-2025-100",
    "internal_manager_emp_number": "EMP-2002",
    "customer_email": "info@skylinedev.com",
    "customer_phone": "+1-555-0150",
    "location_address_line_2": "Floor 15",
    "contacts": [
      {
        "first_name": "Lisa",
        "last_name": "Rodriguez",
        "title": "Senior Project Manager",
        "email": "lisa.r@skylinedev.com",
        "phone": "+1-555-0101",
        "mobile": "+1-555-0201",
        "is_primary": true
      },
      {
        "first_name": "David",
        "last_name": "Kim",
        "title": "Site Supervisor",
        "email": "david.k@skylinedev.com",
        "phone": "+1-555-0102",
        "notes": "Replaces Tom Anderson"
      }
    ],
    "custom_fields": {
      "building_permit": "NYC-BUILD-2025-5678",
      "contract_value": "52000000",
      "status_note": "Budget increased due to scope expansion"
    }
  }
}

Response

200 OK
{
  "success": true,
  "message": "Project updated successfully",
  "event_id": "evt_2345678902",
  "record_id": "rec_project_skyline"
}

DELETE

Delete Project

Delete the Skyline Tower project. Only the identifying field is required for deletion.

Request Body

project-delete.json
{
  "event_type": "project.deleted",
  "timestamp": "2025-03-01T14:00:00Z",
  "source": "crm_system",
  "data": {
    "project_code": "PRJ-2025-100"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Project deleted successfully",
  "event_id": "evt_2345678903",
  "record_id": "rec_project_skyline"
}

CLOSE

Close Project

Mark a project as closed or completed. By default, closing will fail if the project has open work orders. Use the optional close_work_orders field to automatically close all work orders when closing the project.

Close Behavior: When close_work_orders is false or omitted, the close operation will fail if any incomplete work orders exist. Set close_work_orders to true to force-close all incomplete work orders along with the project.

Data Fields

Field Type Required Description
project_code string Yes The unique project code identifying which project to close
close_work_orders boolean No When true, automatically closes all incomplete work orders. Defaults to false

Request Body - Basic Close

project-close-basic.json
{
  "event_type": "project.closed",
  "timestamp": "2025-01-15T14:00:00Z",
  "source": "erp_system",
  "data": {
    "project_code": "PRJ-2025-100"
  }
}

Request Body - Force Close with Work Orders

project-close-force.json
{
  "event_type": "project.closed",
  "timestamp": "2025-01-15T14:00:00Z",
  "source": "erp_system",
  "data": {
    "project_code": "PRJ-2025-100",
    "close_work_orders": true
  }
}

Response

200 OK
{
  "success": true,
  "message": "Project closed successfully",
  "event_id": "evt_2345678904",
  "record_id": "rec_project_skyline"
}
POST
/webhooks/inbound

Employee Operations

Create, update, or delete employees in WorkSkedge. Use event types employee.created, employee.updated, or employee.deleted with the appropriate data fields.

Key Field Behaviors
  • emp_number: This is the public identifier for the employee (e.g., "EMP-12345"), NOT the internal UUID. Use this to identify employees across all operations.
  • department_name: If the department doesn't exist in WorkSkedge, it will be automatically created. Otherwise, it matches to the existing department.
  • role_name: If the role doesn't exist in WorkSkedge, it will be automatically created. Otherwise, it matches to the existing role.
  • start_date: The employee's start date in YYYY-MM-DD format (e.g., "2025-01-15"). Optional field.
  • Emergency contact: Use emergency_contact_name, emergency_contact_phone, and emergency_contact_relationship to store emergency contact information. All are optional.
  • Required for creates: emp_number, name, email, and employee_type
CREATE

Create Employee

Add a new employee to WorkSkedge with complete details including role, department, and skills.

Request Body

employee-create.json
{
  "event_type": "employee.created",
  "timestamp": "2025-01-15T09:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345",
    "name": "John Martinez",
    "email": "john.martinez@company.com",
    "phone": "+1-555-1234",
    "department_name": "Field Operations",
    "role_name": "Site Supervisor",
    "employee_type": "site_lead",
    "employee_skills": ["Concrete", "Framing", "Safety Management"],
    "start_date": "2025-01-15",
    "emergency_contact_name": "Maria Martinez",
    "emergency_contact_phone": "+1-555-5678",
    "emergency_contact_relationship": "Spouse"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Webhook processed successfully",
  "event_id": "evt_1234567890",
  "record_id": "rec_abcdef123456"
}

UPDATE

Update Employee

Update John Martinez's role to Project Manager and add additional skills after completing leadership training.

Request Body

employee-update.json
{
  "event_type": "employee.updated",
  "timestamp": "2025-03-01T10:30:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345",
    "role_name": "Project Manager",
    "email": "john.martinez@company.com",
    "phone": "+1-555-1234",
    "employee_type": "project_owner",
    "employee_skills": ["Concrete", "Framing", "Safety Management", "Project Planning", "Budget Management"],
    "emergency_contact_name": "Maria Martinez",
    "emergency_contact_phone": "+1-555-9999"
  }
}
Employee Type Field

The employee_type field designates the employee's role classification. Available types:

  • site_worker: Field worker who can be assigned to work orders. When the timesheets module is enabled, site workers can be invited to create a user account to submit daily timesheets.
  • site_lead: Field supervisor who leads work orders and has site lead role
  • project_owner: Manager who oversees projects and has project owner role
  • office: Office/administrative staff (not assigned to field work)

This field is required for creates and optional for updates.

Critical: Employee Skills Requirement

All skill names in the employee_skills array must already exist in your company's skill library.

If any skill doesn't exist, the webhook will return an error listing the missing skills. Use the skill management endpoints to create skills before assigning them to employees.

Response

200 OK
{
  "success": true,
  "message": "Employee updated successfully",
  "event_id": "evt_1234567891",
  "record_id": "rec_abcdef123456"
}

DELETE

Delete Employee

Remove an employee from WorkSkedge. Only the employee number identifier is required for deletion.

Request Body

employee-delete.json
{
  "event_type": "employee.deleted",
  "timestamp": "2025-06-15T14:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Employee deleted successfully",
  "event_id": "evt_1234567892",
  "record_id": "rec_abcdef123456"
}
POST
/webhooks/inbound

Vendor Operations

Create, update, or delete vendors in WorkSkedge. Use event types vendor.created, vendor.updated, or vendor.deleted with the appropriate data fields.

CREATE

Create Vendor

Register a new vendor or subcontractor with complete contact information, trade specialty, and custom fields.

Request Body

vendor-create.json
{
  "event_type": "vendor.created",
  "timestamp": "2025-01-15T08:00:00Z",
  "source": "procurement_system",
  "data": {
    "vendor_name": "Superior HVAC Solutions",
    "type": "subcontractor",
    "trade_specialty": ["HVAC", "Mechanical", "Refrigeration"],
    "primary_contact_name": "Robert Williams",
    "primary_contact_email": "robert@superiorhvac.com",
    "primary_contact_phone": "+1-555-HVAC-100",
    "address": "1500 Industrial Parkway, Suite 200",
    "city": "Chicago",
    "state": "IL",
    "zip_code": "60601",
    "country": "USA",
    "rating": 5,
    "regions": ["Midwest", "Great Lakes"],
    "notes": "Preferred vendor for commercial HVAC projects. 15% discount on materials.",
    "insurance_expiry_date": "2025-12-31",
    "compliance_expiry_date": "2025-06-30",
    "custom_fields": {
      "license_number": "HVAC-IL-2025-5432",
      "w9_on_file": "yes"
    }
  }
}
Important: Vendor Field Formats
  • type: Vendor classification. Options: subcontractor, supplier, consultant (optional)
  • trade_specialty: Accepts both array format (e.g., ["HVAC", "Mechanical"]) or comma-separated string (e.g., "HVAC, Mechanical")
  • Address: Can be provided as separate fields (address, city, state, zip_code, country) OR as a combined string
  • state: Accepts both 2-letter codes (e.g., "IL", "MB") and full names (e.g., "Illinois", "Manitoba")
  • insurance_expiry_date: Date when vendor's insurance coverage expires (YYYY-MM-DD format)
  • compliance_expiry_date: Date when vendor's compliance certification expires (YYYY-MM-DD format)
  • Optional fields: address_line2, approval_status, regions, notes, custom_fields

Response

200 OK
{
  "success": true,
  "message": "Vendor created successfully",
  "event_id": "evt_9876543210",
  "record_id": "rec_vendor_superior_hvac"
}

UPDATE

Update Vendor

Update Superior HVAC Solutions with a new primary contact, improved rating, and expanded service regions.

Request Body

vendor-update.json
{
  "event_type": "vendor.updated",
  "timestamp": "2025-04-10T11:00:00Z",
  "source": "procurement_system",
  "data": {
    "vendor_name": "Superior HVAC Solutions",
    "primary_contact_name": "Sarah Johnson",
    "primary_contact_email": "sarah.j@superiorhvac.com",
    "primary_contact_phone": "+1-555-HVAC-200",
    "rating": 5,
    "regions": ["Midwest", "Great Lakes", "Northeast"],
    "notes": "Preferred vendor for commercial HVAC projects. 15% discount on materials. Expanded to Northeast region.",
    "insurance_expiry_date": "2026-12-31",
    "compliance_expiry_date": "2026-06-30",
    "custom_fields": {
      "license_number": "HVAC-IL-2025-5432",
      "w9_on_file": "yes",
      "performance_score": "98"
    }
  }
}

Response

200 OK
{
  "success": true,
  "message": "Vendor updated successfully",
  "event_id": "evt_9876543211",
  "record_id": "rec_vendor_superior_hvac"
}

DELETE

Delete Vendor

Remove a vendor from WorkSkedge. Only the vendor name identifier is required for deletion.

Request Body

vendor-delete.json
{
  "event_type": "vendor.deleted",
  "timestamp": "2025-08-01T09:00:00Z",
  "source": "procurement_system",
  "data": {
    "vendor_name": "Superior HVAC Solutions"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Vendor deleted successfully",
  "event_id": "evt_9876543212",
  "record_id": "rec_vendor_superior_hvac"
}
POST
/webhooks/inbound

Customer Operations

Create, update, or delete customers in WorkSkedge. Use event types customer.created, customer.updated, or customer.deleted with the appropriate data fields.

CREATE

Create Customer

Create a new customer in WorkSkedge with complete company or individual information.

Request Body

customer-create.json
{
  "event_type": "customer.created",
  "timestamp": "2025-01-30T10:00:00Z",
  "source": "crm_system",
  "data": {
    "name": "Acme Corporation",
    "type": "commercial",
    "email": "accounts@acmecorp.com",
    "phone": "555-123-4567",
    "website": "https://www.acmecorp.com",
    "status": "Active",
    "account_manager_emp_number": "EMP-2001",
    "notes": "Major client - requires dedicated account manager"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Customer created successfully",
  "event_id": "evt_1234567890",
  "record_id": "rec_customer_acme_corp"
}

UPDATE

Update Customer

Update an existing customer's information in WorkSkedge.

Request Body

customer-update.json
{
  "event_type": "customer.updated",
  "timestamp": "2025-01-30T11:00:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "email": "billing@acmecorp.com",
    "phone": "555-123-4568",
    "status": "Inactive",
    "notes": "Contract ended - moved to inactive"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Customer updated successfully",
  "event_id": "evt_1234567891",
  "record_id": "rec_customer_acme_corp"
}

DELETE

Delete Customer

Delete a customer from WorkSkedge.

Request Body

customer-delete.json
{
  "event_type": "customer.deleted",
  "timestamp": "2025-01-30T12:00:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Old Customer Inc"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Customer deleted successfully",
  "event_id": "evt_1234567892",
  "record_id": "rec_customer_old_customer"
}
POST
/webhooks/inbound

Location Operations

Create, update, or delete customer locations in WorkSkedge. Use event types location.created, location.updated, or location.deleted with the appropriate data fields.

CREATE

Create Location

Create a new location for a customer in WorkSkedge with complete address information.

Request Body

location-create.json
{
  "event_type": "location.created",
  "timestamp": "2025-01-30T10:30:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "name": "Acme Headquarters",
    "address_line_1": "123 Main Street",
    "address_line_2": "Suite 500",
    "city": "Springfield",
    "province": "IL",
    "postal_code": "62701",
    "country": "USA",
    "phone": "555-100-2000",
    "notes": "Main office location"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Location created successfully",
  "event_id": "evt_2234567890",
  "record_id": "rec_location_acme_hq"
}

UPDATE

Update Location

Update an existing location's information in WorkSkedge.

Request Body

location-update.json
{
  "event_type": "location.updated",
  "timestamp": "2025-01-30T11:30:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "location_name": "Acme Headquarters",
    "phone": "555-100-2001",
    "notes": "Updated contact information"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Location updated successfully",
  "event_id": "evt_2234567891",
  "record_id": "rec_location_acme_hq"
}

DELETE

Delete Location

Delete a location from WorkSkedge.

Request Body

location-delete.json
{
  "event_type": "location.deleted",
  "timestamp": "2025-01-30T12:30:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "location_name": "Old Warehouse"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Location deleted successfully",
  "event_id": "evt_2234567892",
  "record_id": "rec_location_old_warehouse"
}
POST
/webhooks/inbound

Contact Operations

Create, update, or delete customer contacts in WorkSkedge. Use event types contact.created, contact.updated, or contact.deleted with the appropriate data fields.

CREATE

Create Contact

Create a new contact for a customer in WorkSkedge with complete contact information.

Request Body

contact-create.json
{
  "event_type": "contact.created",
  "timestamp": "2025-01-30T11:00:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "first_name": "Sarah",
    "last_name": "Johnson",
    "location_name": "Acme Headquarters",
    "title": "Operations Manager",
    "email": "sarah.johnson@acmecorp.com",
    "phone": "555-100-2010",
    "phone_ext": "101",
    "mobile": "555-200-3000",
    "is_primary": true,
    "contact_tags": ["operations", "primary", "emergency"],
    "notes": "Available 24/7 for emergency issues"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Contact created successfully",
  "event_id": "evt_3234567890",
  "record_id": "rec_contact_sarah_johnson"
}

UPDATE

Update Contact

Update an existing contact's information in WorkSkedge.

Request Body

contact-update.json
{
  "event_type": "contact.updated",
  "timestamp": "2025-01-30T12:00:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "first_name": "Sarah",
    "last_name": "Johnson",
    "title": "Senior Operations Manager",
    "email": "sarah.j@acmecorp.com"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Contact updated successfully",
  "event_id": "evt_3234567891",
  "record_id": "rec_contact_sarah_johnson"
}

DELETE

Delete Contact

Delete a contact from WorkSkedge.

Request Body

contact-delete.json
{
  "event_type": "contact.deleted",
  "timestamp": "2025-01-30T13:00:00Z",
  "source": "crm_system",
  "data": {
    "customer_name": "Acme Corporation",
    "first_name": "John",
    "last_name": "Doe"
  }
}

Response

200 OK
Copy
{
  "success": true,
  "message": "Contact deleted successfully",
  "event_id": "evt_3234567892",
  "record_id": "rec_contact_john_doe"
}
POST
/webhooks/inbound

Time Off Operations

Create, update, or delete time off requests in WorkSkedge. Use event types time_off.created, time_off.updated, or time_off.deleted with the appropriate data fields.

CREATE

Create Time Off Request

Submit a new time off request for an employee with dates, reason, and notes.

Request Body

timeoff-create.json
{
  "event_type": "time_off.created",
  "timestamp": "2025-01-15T14:30:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "start_time": null,
    "end_time": null,
    "time_off_type": "vacation",
    "status": "approved",
    "reason": "Vacation",
    "notes": "Annual family vacation to Hawaii. Will be completely unavailable."
  }
}
Optional Time Off Fields
  • start_time / end_time: Use these for partial day time off in HH:MM:SS format (e.g., "09:00:00" to "13:00:00"). Set to null for full day(s) off
  • time_off_type: Options: vacation, sick_leave, personal, unpaid, other (default: vacation)
  • status: Options: approved, pending, denied (default: approved)
  • reason / notes: Optional text fields for additional context

Response

200 OK
{
  "success": true,
  "message": "Time off request processed successfully",
  "event_id": "evt_1234567890",
  "record_id": "rec_timeoff_emp12345"
}

UPDATE

Update Time Off Request

Update an existing time off request. The employee number and original dates are required to identify the request.

Request Body

timeoff-update.json
{
  "event_type": "time_off.updated",
  "timestamp": "2025-01-18T09:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "status": "approved",
    "notes": "Updated: Extended vacation by one day. Manager approved."
  }
}
Update Identification
  • Required: emp_number, start_date, and end_date are required to identify the time off record to update
  • Updatable fields: status, time_off_type, reason, notes, start_time, end_time

Response

200 OK
{
  "success": true,
  "message": "Time off request updated successfully",
  "event_id": "evt_1234567891",
  "record_id": "rec_timeoff_emp12345"
}

DELETE

Delete Time Off Request

Cancel a time off request. The employee number, start date, and end date are all required for deletion.

Request Body

timeoff-delete.json
{
  "event_type": "time_off.deleted",
  "timestamp": "2025-01-17T10:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24"
  }
}

Response

200 OK
Copy
{
  "success": true,
  "message": "Time off request deleted successfully",
  "event_id": "evt_1234567892",
  "record_id": "rec_timeoff_emp12345"
}
POST
/webhooks/inbound

Timesheet Operations

Create, update, or delete daily timesheets in WorkSkedge. Timesheets are date-based records that can include multiple work entries for a single day. Use event types timesheet.created, timesheet.updated, or timesheet.deleted.

Work Type Support

Each timesheet entry can specify a work type to categorize hours:

  • Regular Time (REG): Standard working hours with 1.0x multiplier
  • Overtime (OT): Hours beyond standard schedule, typically 1.5x multiplier
  • Vacation (VAC): Paid time off, non-billable
  • Sick Leave (SICK): Sick time, non-billable
  • Holiday (HOL): Company holiday, non-billable
  • Custom Types: Organizations can define additional work types

How to Specify Work Types:

  1. Preferred: Use work_type_code (e.g., "OT", "VAC") - more reliable
  2. Alternative: Use work_type_name (e.g., "Overtime", "Vacation")
  3. Default: If neither provided, system uses company's default work type

Work Order Requirements:

  • Billable work types (REG, OT) typically require work_order_number
  • Non-billable types (VAC, SICK, HOL) can have work_order_number: null
  • System validates based on work type's requires_work_order setting
CREATE

Create Timesheet

Create a daily timesheet for an employee. Specify the timesheet date (required) and optional entries with hours worked and associated work orders. Multiple entries can be added for the same day if work was performed on different tasks or work orders.

Request Body

timesheet-create.json
{
  "event_type": "timesheet.created",
  "timestamp": "2025-01-15T08:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-1001",
    "timesheet_date": "2025-10-03",
    "status": "draft",
    "total_hours": 8.5,
    "entries": [
      {
        "entry_date": "2025-10-03",
        "work_order_number": "WO-00012",
        "hours": 5.0,
        "start_time": "08:00",
        "end_time": "13:00",
        "work_type_code": "REG",
        "work_type_name": "Regular Time",
        "notes": "HVAC installation - morning shift"
      },
      {
        "entry_date": "2025-10-03",
        "work_order_number": "WO-00009",
        "hours": 3.5,
        "start_time": "13:30",
        "end_time": "17:00",
        "work_type_code": "REG",
        "work_type_name": "Regular Time",
        "notes": "Electrical work - panel installation"
      }
    ]
  }
}

Response

200 OK
{
  "success": true,
  "message": "Timesheet created successfully",
  "event_id": "evt_9876543210",
  "record_id": "rec_timesheet_emp1001_20251003"
}

UPDATE

Update Timesheet

Update a daily timesheet, typically to change status (draft → submitted → locked → processed), update total hours, or modify notes. You can also add or modify entries for the day.

Request Body

timesheet-update.json
{
  "event_type": "timesheet.updated",
  "timestamp": "2025-01-15T17:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-1001",
    "timesheet_date": "2025-10-03",
    "status": "submitted",
    "total_hours": 8.5,
    "notes": "Day completed. All work orders documented."
  }
}

Response

200 OK
{
  "success": true,
  "message": "Timesheet updated successfully",
  "event_id": "evt_9876543211",
  "record_id": "rec_timesheet_emp1001_20251003"
}

DELETE

Delete Timesheet

Delete a daily timesheet. Only the employee number and timesheet date are required to identify which timesheet to delete.

Request Body

timesheet-delete.json
{
  "event_type": "timesheet.deleted",
  "timestamp": "2025-01-15T18:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-1001",
    "timesheet_date": "2025-10-03"
  }
}

Response

200 OK
{
  "success": true,
  "message": "Timesheet deleted successfully",
  "event_id": "evt_9876543212",
  "record_id": "rec_timesheet_emp1001_20251003"
}

Field Reference

TimesheetData Fields
Field Type Required Description
emp_number string ✓ Yes Employee identifier (e.g., "EMP-1001")
timesheet_date date ✓ Yes Date of the daily timesheet (YYYY-MM-DD format)
status string Optional Timesheet status: draft, submitted, locked, or processed (default: draft)
total_hours number Optional Total hours for the day (minimum: 0)
notes string Optional General notes for the timesheet
entries array Optional Array of work entries for this timesheet (see TimesheetEntryData fields below)
TimesheetEntryData Fields
Field Type Required Description
entry_date date ✓ Yes Date of work - must match parent timesheet_date (YYYY-MM-DD format)
hours number ✓ Yes Hours worked (minimum: 0, maximum: 24)
work_order_number string Conditional Work order number (e.g., "WO-00012") - NOT a UUID. Optional for non-billable work types (vacation, sick leave). Required for billable work types unless work type specifies otherwise.
work_type_code string Optional Work type short code (max 20 chars, e.g., "REG", "OT", "VAC", "SICK"). Preferred method for specifying work type. Falls back to work_type_name, then company default.
work_type_name string Optional Work type full name (max 100 chars, e.g., "Regular Time", "Overtime", "Vacation"). Used if work_type_code is not provided. System matches against active work types in WorkSkedge.
notes string Optional Notes for this specific entry
Key Concepts
  • Date-based system: Timesheets represent a single day of work, identified by timesheet_date
  • Multiple entries per day: A single timesheet can contain multiple entries for different work orders or tasks performed on the same day
  • Status workflow: draft (initial) → submitted (employee confirms) → locked (approved) → processed (payroll/accounting complete)
  • Work order tracking: Each entry can optionally reference a work_order_number to track time against specific jobs
  • Work type classification: Use work_type_code and work_type_name to categorize hours (regular, overtime, vacation, etc.)
  • Hours validation: Entry hours must be between 0 and 24 per entry
  • Flexible updates: Update operations can modify status, add entries, adjust hours, or include additional notes
POST
/webhooks/inbound

Search Operations

Search for existing projects and work orders in WorkSkedge using exact or fuzzy matching. Search results return an array of matching records with full details.

Search Projects

Search for projects by project name or project code. Supports both exact matching and fuzzy/partial matching.

Request Body (Exact Match by Project Code)

project-search-exact.json
{
  "event_type": "project.search",
  "timestamp": "2025-01-15T13:30:00Z",
  "source": "integration_platform",
  "data": {
    "search_term": "PRJ-2025-100",
    "search_field": "project_code",
    "exact_match": true
  }
}

Request Body (Fuzzy Match by Project Name)

project-search-fuzzy.json
{
  "event_type": "project.search",
  "timestamp": "2025-01-15T13:35:00Z",
  "source": "integration_platform",
  "data": {
    "search_term": "Skyline",
    "search_field": "project_name",
    "exact_match": false
  }
}

Response

200 OK
{
  "success": true,
  "action": "search",
  "results": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "project_name": "Skyline Tower Development",
      "project_code": "PRJ-2025-100",
      "description": "15-story mixed-use building",
      "start_date": "2025-02-01",
      "end_date": "2025-12-31"
    }
  ],
  "count": 1
}
Search Parameters
  • search_term (required) - Text to search for, 1-255 characters
  • search_field (optional) - Field to search in: project_name (default) or project_code
  • exact_match (optional) - true for exact match, false (default) for fuzzy/partial match

Note: Empty search results return {"results": [], "count": 0}


Search Work Orders

Search for work orders by phase name or work order number. Optionally filter results to a specific project using project_code.

Request Body (Exact Match by Work Order Number)

work-order-search-exact.json
{
  "event_type": "work_order.search",
  "timestamp": "2025-01-15T13:40:00Z",
  "source": "integration_platform",
  "data": {
    "search_term": "WO-2025-001",
    "search_field": "work_order_number",
    "exact_match": true
  }
}

Request Body (Fuzzy Match by Phase Name)

work-order-search-fuzzy.json
{
  "event_type": "work_order.search",
  "timestamp": "2025-01-15T13:45:00Z",
  "source": "integration_platform",
  "data": {
    "search_term": "Foundation",
    "search_field": "phase_name",
    "exact_match": false
  }
}

Request Body (Search with Project Filter)

work-order-search-filtered.json
{
  "event_type": "work_order.search",
  "timestamp": "2025-01-15T13:50:00Z",
  "source": "integration_platform",
  "data": {
    "search_term": "Electrical",
    "search_field": "phase_name",
    "project_code": "PRJ-2025-100",
    "exact_match": false
  }
}

Response

200 OK
{
  "success": true,
  "action": "search",
  "results": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440001",
      "work_order_number": "WO-2025-015",
      "phase_name": "Electrical Rough-In",
      "status": "scheduled",
      "priority": "normal",
      "start_date": "2025-02-10",
      "end_date": "2025-02-12",
      "project": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "project_code": "PRJ-2025-100",
        "project_name": "Skyline Tower Development"
      }
    }
  ],
  "count": 1
}
Search Parameters
  • search_term (required) - Text to search for, 1-255 characters
  • search_field (optional) - Field to search in: phase_name (default) or work_order_number
  • project_code (optional) - Filter results to a specific project (e.g., "PRJ-2025-100")
  • exact_match (optional) - true for exact match, false (default) for fuzzy/partial match

Note: Search results include associated project details. Empty results return {"results": [], "count": 0}

Operation Patterns

All entity types support create, update, and delete operations by changing the event_type suffix. This pattern applies uniformly across the entire API.

CREATE
.created

Creates a new record

UPDATE
.updated

Updates an existing record

DELETE
.deleted

Deletes a record

Example: Update Project

project-update.json
{
  "event_type": "project.updated",
  "timestamp": "2025-01-20T10:00:00Z",
  "source": "crm_system",
  "data": {
    "project_code": "PRJ-2025-100",
    "customer_email": "michael.chen@skylinedev.com",
    "internal_manager_emp_number": "EMP-2002",
    "custom_fields": {
      "contract_value": "52000000",
      "status_note": "Budget increased due to scope expansion"
    }
  }
}

Example: Delete Employee

employee-delete.json
{
  "event_type": "employee.deleted",
  "timestamp": "2025-01-15T12:00:00Z",
  "source": "hr_system",
  "data": {
    "emp_number": "EMP-12345"
  }
}

Supported Operations by Entity

  • Work Orders: work_order.created, work_order.updated, work_order.deleted
  • Projects: project.created, project.updated, project.deleted, project.closed
  • Employees: employee.created, employee.updated, employee.deleted
  • Vendors: vendor.created, vendor.updated, vendor.deleted
  • Customers: customer.created, customer.updated, customer.deleted
  • Locations: location.created, location.updated, location.deleted
  • Contacts: contact.created, contact.updated, contact.deleted
  • Time Off: time_off.created, time_off.updated, time_off.deleted
  • Timesheets: timesheet.created, timesheet.updated, timesheet.deleted
  • Reports: report.daily_submitted, report.completion_submitted, report.completion_approved
  • Connection Testing: test.connection
POST
/webhooks/inbound

Report Operations

Submit daily progress reports and completion reports for work orders. Reports can be auto-approved or require manager approval. Use event types report.daily_submitted, report.completion_submitted, or report.completion_approved.

Auto-Approval Workflow

Reports can bypass manual approval using the is_auto_approved flag:

  • Daily Reports: Set is_auto_approved: true to skip manager review
  • Completion Reports: Set requires_approval: false and is_auto_approved: true to immediately close work order
  • Default: All reports require approval unless explicitly configured otherwise
SUBMIT

Submit Daily Report

Submit a daily progress report documenting work completed, materials used, hours worked, and other daily activities.

Request Body

daily-report-submit.json
{
  "event_type": "report.daily_submitted",
  "timestamp": "2025-01-20T17:00:00Z",
  "source": "field_app",
  "data": {
    "work_order_number": "WO-2025-001",
    "report_date": "2025-01-20",
    "submitted_by": "EMP-1001",
    "notes": "Good progress today. Completed main installation.",
    "work_completed": "Installed main HVAC unit and connected ductwork",
    "materials_used": "1x HVAC unit, 50ft ductwork, mounting brackets",
    "hours_worked": 8.5,
    "weather_conditions": "Clear skies, 72F",
    "safety_incidents": "",
    "is_auto_approved": false
  }
}

Field Reference

Field Type Required Description
work_order_number string Yes Work order number (e.g., "WO-2025-001")
report_date date Yes Date of the report (YYYY-MM-DD)
submitted_by string No Employee number of submitter (e.g., "EMP-1001")
notes string No General notes about the day's work
work_completed string No Description of work completed
materials_used string No Materials used during the work
hours_worked number No Total hours worked (0-24)
weather_conditions string No Weather conditions affecting work
safety_incidents string No Any safety incidents or concerns
is_auto_approved boolean No If true, bypasses manual approval. Default: false
SUBMIT

Submit Completion Report

Submit a completion report to mark a work order as complete. Can include scope and completion checklists.

Request Body

completion-report-submit.json
{
  "event_type": "report.completion_submitted",
  "timestamp": "2025-01-25T16:00:00Z",
  "source": "field_app",
  "data": {
    "work_order_number": "WO-2025-001",
    "report_date": "2025-01-25",
    "submitted_by": "EMP-1001",
    "completion_summary": "All work completed per scope. System tested and operational.",
    "scope_checklist": [
      { "item_name": "Install HVAC unit", "status": "completed" },
      { "item_name": "Connect ductwork", "status": "completed" },
      { "item_name": "Test system", "status": "completed" }
    ],
    "completion_checklist": [
      { "item_name": "Customer walkthrough", "status": "completed", "notes": "Customer signed off" },
      { "item_name": "Area cleanup", "status": "completed" }
    ],
    "requires_approval": true,
    "is_auto_approved": false
  }
}

Field Reference

Field Type Required Description
work_order_number string Yes Work order number to mark complete
report_date date No Completion date (defaults to current date)
submitted_by string No Employee number of submitter
completion_summary string No Summary of completed work
scope_checklist array No Array of scope items with item_name and status
completion_checklist array No Array of completion items with item_name and status
requires_approval boolean No Whether manager approval is needed. Default: true
is_auto_approved boolean No If true and requires_approval is false, auto-completes. Default: false
APPROVE

Approve Completion Report

Approve a pending completion report to mark the work order as complete.

Request Body

completion-report-approve.json
{
  "event_type": "report.completion_approved",
  "timestamp": "2025-01-26T09:00:00Z",
  "source": "management_app",
  "data": {
    "work_order_number": "WO-2025-001",
    "approved_by": "EMP-2001",
    "approval_notes": "Work verified and approved"
  }
}

Field Reference

Field Type Required Description
work_order_number string Yes Work order with pending completion report
approved_by string No Employee number of approver
approval_notes string No Notes from the approver
POST
/webhooks/inbound

Test Connection

Used by integration platforms like Zapier during setup to verify API keys and endpoint availability. This endpoint confirms authentication is working correctly before creating actual integrations.

TEST

Test Connection

Verify your API credentials and test the webhook endpoint connection. This operation returns a success response if authentication is valid.

Request Body

test-connection.json
curl -X POST https://app.workskedge.com/webhooks/inbound \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Timestamp: $(date +%s)" \
  -H "X-Webhook-Source: zapier" \
  -d '{
    "event_type": "test.connection",
    "timestamp": "2025-01-15T10:00:00Z",
    "source": "zapier",
    "data": {
      "test": true
    }
  }'

Response

200 OK
{
  "success": true,
  "message": "Webhook processed successfully",
  "event_id": "evt_1234567890",
  "record_id": "rec_abcdef123456"
}

Field Reference

Field Type Required Description
test boolean No Test flag indicating this is a connection test
Usage Notes
  • The data object can contain any arbitrary properties
  • Commonly used during initial integration setup
  • Returns the same response structure as other webhook events
  • Does not create or modify any records in WorkSkedge

Common Error Codes

400
Bad Request

Invalid request body or missing required fields

401
Unauthorized

Missing or invalid API key

403
Forbidden

Signature verification failed or insufficient permissions

429
Too Many Requests

Rate limit exceeded

200
Success

Request processed successfully

Outbound Webhooks

Receive real-time notifications from WorkSkedge when events occur in your account. Subscribe to events and receive HTTP POST requests to your endpoint URLs.

Setup & Configuration

Configure outbound webhooks in your WorkSkedge dashboard. Navigate to Settings → Webhooks to add endpoint URLs and subscribe to specific event types.

Configure Webhooks

Webhook Headers

All outbound webhook requests include the following headers for verification and processing:

X-Webhook-Signature

HMAC-SHA256 signature for verifying the request authenticity

X-Webhook-Timestamp

Unix timestamp when the webhook was sent (for replay protection)

X-Webhook-ID

Unique identifier for this webhook delivery

X-Webhook-Event

The event type that triggered this webhook

X-Webhook-Retry

Retry attempt number (0 for first attempt)

Signature Verification

Verify webhook authenticity by validating the X-Webhook-Signature header. This ensures the request came from WorkSkedge and hasn't been tampered with.

verify-signature.js
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret, timestamp) {
  const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300;
  if (timestamp < fiveMinutesAgo) {
    throw new Error('Webhook timestamp too old');
  }

  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = JSON.stringify(req.body);

  try {
    if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    console.log('Webhook verified:', req.body);
    res.status(200).json({ received: true });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});
verify_signature.py
import hmac
import hashlib
import time
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = 'your-webhook-secret'

def verify_webhook_signature(payload, signature, timestamp):
    five_minutes_ago = int(time.time()) - 300
    if int(timestamp) < five_minutes_ago:
        raise ValueError('Webhook timestamp too old')

    signed_payload = f"{timestamp}.{payload}"
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    payload = request.get_data(as_text=True)

    try:
        if not verify_webhook_signature(payload, signature, timestamp):
            return jsonify({'error': 'Invalid signature'}), 401

        data = request.get_json()
        print('Webhook verified:', data)
        return jsonify({'received': True}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 400
verify-signature.php
<?php

define('WEBHOOK_SECRET', 'your-webhook-secret');

function verifyWebhookSignature($payload, $signature, $timestamp) {
    $fiveMinutesAgo = time() - 300;
    if ($timestamp < $fiveMinutesAgo) {
        throw new Exception('Webhook timestamp too old');
    }

    $signedPayload = $timestamp . '.' . $payload;
    $expectedSignature = hash_hmac('sha256', $signedPayload, WEBHOOK_SECRET);

    return hash_equals($signature, $expectedSignature);
}

$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$payload = file_get_contents('php://input');

try {
    if (!verifyWebhookSignature($payload, $signature, $timestamp)) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid signature']);
        exit;
    }

    $data = json_decode($payload, true);
    error_log('Webhook verified: ' . print_r($data, true));

    http_response_code(200);
    echo json_encode(['received' => true]);
} catch (Exception $e) {
    http_response_code(400);
    echo json_encode(['error' => $e->getMessage()]);
}
?>
test-webhook.sh
#!/bin/bash

WEBHOOK_URL="https://your-domain.com/webhook"
PAYLOAD='{"event":"workorder.created","data":{"id":"wo_123"}}'
TIMESTAMP=$(date +%s)
SECRET="your-webhook-secret"

SIGNATURE=$(echo -n "${TIMESTAMP}.${PAYLOAD}" | \
  openssl dgst -sha256 -hmac "$SECRET" | \
  sed 's/^.* //')

curl -X POST "$WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: $SIGNATURE" \
  -H "X-Webhook-Timestamp: $TIMESTAMP" \
  -H "X-Webhook-Event: workorder.created" \
  -H "X-Webhook-ID: test_$(uuidgen)" \
  -H "X-Webhook-Retry: 0" \
  -d "$PAYLOAD"

Available Events

Work Order Events

Receive notifications when work orders are created, updated, or deleted in your WorkSkedge account.

EVENT

work_order.created

Triggered when a new work order is created in WorkSkedge. Includes complete work order details, project information, and scheduling data.

Payload Example

work_order.created
{
  "event_id": "evt_7d3f2c1a9b4e8f6d",
  "event_type": "work_order.created",
  "timestamp": "2025-01-15T14:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "status": "scheduled",
    "priority": "high",
    "start_date": "2025-02-01",
    "end_date": "2025-02-05",
    "start_time": "08:00",
    "end_time": "17:00",
    "expected_start_date": "2025-02-01",
    "expected_end_date": "2025-02-05",
    "expected_start_time": "08:00",
    "expected_end_time": "17:00",
    "people_required": 5,
    "estimated_hours": 45,
    "travel_time_hours": 1.5,
    "additional_labor_cost": 5000.00,
    "performed_by": "internal",
    "require_daily_report": true,
    "require_completion_report": true,
    "display_order": 1,
    "project": {
      "id": "proj_123456",
      "project_code": "PRJ-2025-001",
      "project_name": "Downtown Office Renovation",
      "customer_name": "ABC Property Development",
      "site_address": "456 Construction Way, Building C"
    },
    "site_lead": {
      "id": "emp_789012",
      "emp_number": "EMP-1001",
      "name": "John Foreman",
      "email": "john.foreman@company.com",
      "phone": "555-234-5678"
    },
    "scope_of_work": {
      "checklists": [
        {
          "title": "Site Preparation",
          "items": [
            {
              "text": "Clear and level site",
              "completed": false
            },
            {
              "text": "Set up formwork",
              "completed": false
            }
          ]
        },
        {
          "title": "Pour and Finish",
          "items": [
            {
              "text": "Pour concrete",
              "completed": false
            },
            {
              "text": "Finish and cure",
              "completed": false
            }
          ]
        }
      ]
    },
    "equipment": ["Concrete mixer", "Vibrator", "Laser level"],
    "specialty_materials": ["High-strength concrete", "Rebar grade 60"],
    "scheduler_notes": {
      "subtrade_required": "Concrete",
      "scheduling_insights": "Weather-dependent, requires 3-day cure time"
    },
    "custom_fields": {
      "permit_number": "BUILD-2025-001",
      "inspection_required": "yes"
    },
    "created_at": "2025-01-15T14:30:00Z",
    "created_by": "user_abc123"
  }
}
EVENT

work_order.updated

Triggered when work order details are modified. Includes the updated work order data and information about what changed.

Payload Example

work_order.updated
{
  "event_id": "evt_9f5e3a2b7c1d4e8a",
  "event_type": "work_order.updated",
  "timestamp": "2025-01-16T10:15:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "status": "scheduled",
    "priority": "urgent",
    "start_date": "2025-01-30",
    "end_date": "2025-02-03",
    "people_required": 7,
    "changes": {
      "priority": {
        "old": "high",
        "new": "urgent"
      },
      "start_date": {
        "old": "2025-02-01",
        "new": "2025-01-30"
      },
      "end_date": {
        "old": "2025-02-05",
        "new": "2025-02-03"
      },
      "people_required": {
        "old": 5,
        "new": 7
      }
    },
    "project": {
      "id": "proj_123456",
      "project_code": "PRJ-2025-001",
      "project_name": "Downtown Office Renovation"
    },
    "updated_at": "2025-01-16T10:15:00Z",
    "updated_by": "user_xyz789"
  }
}
EVENT

work_order.status_changed

Triggered specifically when a work order's status changes. Useful for workflow automation and status-based triggers.

Payload Example

work_order.status_changed
{
  "event_id": "evt_4b8c6d1e3f9a2e5b",
  "event_type": "work_order.status_changed",
  "timestamp": "2025-02-05T16:45:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "previous_status": "in_progress",
    "new_status": "completed",
    "status_changed_at": "2025-02-05T16:45:00Z",
    "status_changed_by": "user_def456",
    "project": {
      "id": "proj_123456",
      "project_code": "PRJ-2025-001",
      "project_name": "Downtown Office Renovation"
    }
  }
}
EVENT

work_order.scheduled

Triggered when a work order is scheduled or rescheduled. Includes scheduling details and assigned personnel.

Payload Example

work_order.scheduled
{
  "event_id": "evt_2e7a9c5b4f1d8e3c",
  "event_type": "work_order.scheduled",
  "timestamp": "2025-01-20T09:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "status": "scheduled",
    "start_date": "2025-01-30",
    "end_date": "2025-02-03",
    "start_time": "08:00",
    "end_time": "17:00",
    "assigned_employees": [
      {
        "id": "emp_789012",
        "emp_number": "EMP-1001",
        "name": "John Foreman",
        "role": "site_lead"
      },
      {
        "id": "emp_345678",
        "emp_number": "EMP-2005",
        "name": "Mike Johnson",
        "role": "crew_member"
      }
    ],
    "project": {
      "id": "proj_123456",
      "project_code": "PRJ-2025-001",
      "project_name": "Downtown Office Renovation"
    },
    "scheduled_at": "2025-01-20T09:30:00Z",
    "scheduled_by": "user_ghi789"
  }
}
EVENT

work_order.deleted

Triggered when a work order is deleted. Includes basic work order information and deletion metadata.

Payload Example

work_order.deleted
{
  "event_id": "evt_8c3f5a1b9e2d7f4a",
  "event_type": "work_order.deleted",
  "timestamp": "2025-01-18T11:20:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "project": {
      "id": "proj_123456",
      "project_code": "PRJ-2025-001",
      "project_name": "Downtown Office Renovation"
    },
    "deleted_at": "2025-01-18T11:20:00Z",
    "deleted_by": "user_jkl012"
  }
}

Project Events

Receive notifications when projects are created, updated, or deleted in your WorkSkedge account.

EVENT

project.created

Triggered when a new project is created. Includes complete project details, customer information, and project owners.

Payload Example

project.created
{
  "event_id": "evt_1a4b7c9e2f5d8a3b",
  "event_type": "project.created",
  "timestamp": "2025-01-10T13:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "proj_123456",
    "project_name": "Downtown Office Renovation",
    "project_code": "PRJ-2025-001",
    "description": "Complete renovation of downtown office space including HVAC upgrades",
    "priority": "high",
    "status": "active",
    "start_date": "2025-02-01",
    "end_date": "2025-06-30",
    "customer": {
      "id": "cust_789012",
      "name": "ABC Property Development",
      "type": "commercial",
      "email": "projects@abcproperty.com",
      "phone": "555-123-4567"
    },
    "location": {
      "id": "loc_345678",
      "name": "Downtown Office Building",
      "address_line_1": "456 Construction Way",
      "city": "San Francisco",
      "province": "CA",
      "postal_code": "94102",
      "country": "USA"
    },
    "contacts": [
      {
        "id": "cont_901234",
        "first_name": "Jane",
        "last_name": "Smith",
        "email": "jane.smith@abcproperty.com",
        "phone": "555-234-5678",
        "role": "Project Manager"
      }
    ],
    "project_owners": [
      {
        "id": "emp_567890",
        "name": "Sarah Johnson",
        "email": "sarah.johnson@company.com",
        "emp_number": "EMP-3001"
      }
    ],
    "created_at": "2025-01-10T13:00:00Z",
    "created_by": "user_mno345"
  }
}
EVENT

project.updated

Triggered when project details are modified. Includes updated project information and change details.

Payload Example

project.updated
{
  "event_id": "evt_5c8d2a3f9b1e7c4d",
  "event_type": "project.updated",
  "timestamp": "2025-02-10T15:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "proj_123456",
    "project_name": "Downtown Office Renovation",
    "project_code": "PRJ-2025-001",
    "priority": "urgent",
    "end_date": "2025-06-15",
    "changes": {
      "priority": {
        "old": "high",
        "new": "urgent"
      },
      "end_date": {
        "old": "2025-06-30",
        "new": "2025-06-15"
      }
    },
    "updated_at": "2025-02-10T15:30:00Z",
    "updated_by": "user_pqr678"
  }
}
EVENT

project.deleted

Triggered when a project is deleted. Includes basic project information and deletion metadata.

Payload Example

project.deleted
{
  "event_id": "evt_9e4a6b2c8f1d5a7b",
  "event_type": "project.deleted",
  "timestamp": "2025-03-01T10:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "proj_123456",
    "project_name": "Downtown Office Renovation",
    "project_code": "PRJ-2025-001",
    "deleted_at": "2025-03-01T10:00:00Z",
    "deleted_by": "user_stu901"
  }
}

EVENT

project.closed

Triggered when a project is closed or completed.

Key Fields

  • id - Unique project UUID (required)
  • project_code - Unique project identifier
  • project_name - Name of the project
  • closed_at - Timestamp when project was closed (required)

Payload Example

project.closed
{
  "event_id": "evt_abc123",
  "event_type": "project.closed",
  "timestamp": "2025-01-15T14:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "project_code": "PRJ-2025-100",
    "project_name": "Skyline Tower Construction",
    "closed_at": "2025-01-15T14:00:00Z"
  }
}

Report Events

Receive notifications when daily reports and completion reports are submitted or approved.

EVENT

report.daily_submitted

Triggered when a daily work report is submitted. Includes report details, photos, and work order information.

Payload Example

report.daily_submitted
{
  "event_id": "evt_3b7e9a1c5f2d8b4e",
  "event_type": "report.daily_submitted",
  "timestamp": "2025-02-02T17:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "report_id": "rpt_daily_456789",
    "work_order_id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "project_name": "Downtown Office Renovation",
    "report_date": "2025-02-02",
    "report_type": "daily",
    "submitted_by": {
      "user_id": "user_vwx234",
      "employee_id": "emp_789012",
      "name": "John Foreman"
    },
    "notes": "Completed formwork setup. Concrete delivery scheduled for tomorrow morning.",
    "weather": "Sunny, 72°F",
    "crew_count": 5,
    "hours_worked": 9,
    "photos": [
      "https://storage.workskedge.com/photos/2025/02/02/photo1.jpg",
      "https://storage.workskedge.com/photos/2025/02/02/photo2.jpg"
    ],
    "is_auto_approved": true,
    "submitted_at": "2025-02-02T17:00:00Z"
  }
}
EVENT

report.completion_submitted

Triggered when a completion report is submitted. Includes detailed checklist completion status and photos.

Payload Example

report.completion_submitted
{
  "event_id": "evt_7d2a8c4e9b1f6a3d",
  "event_type": "report.completion_submitted",
  "timestamp": "2025-02-05T16:45:00Z",
  "company_id": "comp_abc123",
  "data": {
    "report_id": "rpt_comp_890123",
    "work_order_id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "project_name": "Downtown Office Renovation",
    "report_date": "2025-02-05",
    "report_type": "completion",
    "submitted_by": {
      "user_id": "user_vwx234",
      "employee_id": "emp_789012",
      "name": "John Foreman"
    },
    "notes": "All foundation work completed successfully. Ready for inspection.",
    "scope_checklist": {
      "items": [
        {
          "title": "Site Preparation",
          "completed": true
        },
        {
          "title": "Pour and Finish",
          "completed": true
        }
      ],
      "completion_percentage": 100
    },
    "completion_checklist": {
      "items": [
        {
          "item": "Site cleaned and secured",
          "completed": true,
          "notes": "All equipment removed"
        },
        {
          "item": "Final inspection requested",
          "completed": true,
          "notes": "Scheduled for 2025-02-06"
        }
      ]
    },
    "photos": [
      "https://storage.workskedge.com/photos/2025/02/05/completion1.jpg",
      "https://storage.workskedge.com/photos/2025/02/05/completion2.jpg",
      "https://storage.workskedge.com/photos/2025/02/05/completion3.jpg"
    ],
    "requires_approval": true,
    "is_auto_approved": false,
    "submitted_at": "2025-02-05T16:45:00Z"
  }
}
EVENT

report.completion_approved

Triggered when a completion report is approved. Includes approval details and approver information.

Payload Example

report.completion_approved
{
  "event_id": "evt_4e9b7c1a8f2d5a6b",
  "event_type": "report.completion_approved",
  "timestamp": "2025-02-06T09:15:00Z",
  "company_id": "comp_abc123",
  "data": {
    "report_id": "rpt_comp_890123",
    "work_order_id": "wo_550e8400-e29b-41d4-a716-446655440000",
    "work_order_number": "WO-2025-001",
    "phase_name": "Foundation Installation",
    "project_name": "Downtown Office Renovation",
    "approved_by": {
      "user_id": "user_yza567",
      "employee_id": "emp_345678",
      "name": "Sarah Johnson",
      "role": "Project Manager"
    },
    "approval_notes": "Work meets all specifications. Approved for billing.",
    "approved_at": "2025-02-06T09:15:00Z"
  }
}

Employee Events

Receive notifications when employees are created, updated, or deleted in your WorkSkedge account.

EVENT

employee.created

Triggered when a new employee is added to the system. Includes complete employee details, contact information, and role assignments.

Payload Example

employee.created
{
  "event_id": "evt_5d8b2a4c9e1f6a3d",
  "event_type": "employee.created",
  "timestamp": "2025-01-12T10:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174001",
    "emp_number": "EMP-2001",
    "name": "Michael Torres",
    "email": "michael.torres@company.com",
    "phone": "+1-555-345-6789",
    "role": "Site Supervisor",
    "department": "Construction",
    "employee_type": "site_worker",
    "employee_skills": ["Concrete", "Framing"],
    "start_date": "2025-01-12",
    "emergency_contact_name": "Anna Torres",
    "emergency_contact_phone": "+1-555-987-6543",
    "is_project_owner": false,
    "is_site_lead": false,
    "is_site_worker": true,
    "created_at": "2025-01-12T10:00:00Z",
    "updated_at": "2025-01-12T10:00:00Z"
  }
}
EVENT

employee.updated

Triggered when employee details are modified. Includes the updated employee data and information about what changed.

Payload Example

employee.updated
{
  "event_id": "evt_8f3a6b1c4e9d2a5b",
  "event_type": "employee.updated",
  "timestamp": "2025-02-15T14:30:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174001",
    "emp_number": "EMP-2001",
    "name": "Michael Torres",
    "email": "michael.torres@company.com",
    "phone": "+1-555-345-6789",
    "role": "Senior Site Supervisor",
    "department": "Construction Management",
    "employee_type": "site_lead",
    "employee_skills": ["Concrete", "Framing", "Safety Management"],
    "start_date": "2025-01-12",
    "emergency_contact_name": "Anna Torres",
    "emergency_contact_phone": "+1-555-987-6543",
    "is_project_owner": false,
    "is_site_lead": true,
    "is_site_worker": true,
    "created_at": "2025-01-12T10:00:00Z",
    "updated_at": "2025-02-15T14:30:00Z"
  }
}
EVENT

employee.deleted

Triggered when an employee is deactivated or removed from the system. Includes basic employee information and deletion metadata.

Payload Example

employee.deleted
{
  "event_id": "evt_2c7e9a4b8f1d5a6c",
  "event_type": "employee.deleted",
  "timestamp": "2025-03-20T16:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174001",
    "emp_number": "EMP-2001",
    "name": "Michael Torres",
    "email": "michael.torres@company.com",
    "deleted_at": "2025-03-20T16:00:00Z"
  }
}

Vendor Events

Receive notifications when vendors are created, updated, or deleted in your WorkSkedge account.

EVENT

vendor.created

Triggered when a new vendor is added to the system. Includes complete vendor details, contact information, and trade specialty.

Payload Example

vendor.created
{
  "event_id": "evt_9c4e7a2b5f1d8a6c",
  "event_type": "vendor.created",
  "timestamp": "2025-01-18T11:15:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "vnd_234567",
    "vendor_name": "Elite Concrete Services",
    "type": "subcontractor",
    "trade_specialty": "Concrete",
    "primary_contact_name": "Robert Chen",
    "primary_contact_email": "robert@eliteconcrete.com",
    "primary_contact_phone": "555-456-7890",
    "address": {
      "address_line_1": "789 Industrial Park",
      "city": "Oakland",
      "province": "CA",
      "postal_code": "94601",
      "country": "USA"
    },
    "rating": 4.8,
    "insurance_expiry_date": "2025-12-31",
    "compliance_expiry_date": "2025-06-30",
    "certifications": ["Licensed", "Insured", "Bonded"],
    "created_at": "2025-01-18T11:15:00Z",
    "created_by": "user_jkl234"
  }
}
EVENT

vendor.updated

Triggered when vendor details are modified. Includes the updated vendor data and information about what changed.

Payload Example

vendor.updated
{
  "event_id": "evt_3b8f2a6c9e4d1a7b",
  "event_type": "vendor.updated",
  "timestamp": "2025-03-10T09:45:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "vnd_234567",
    "vendor_name": "Elite Concrete Services",
    "type": "subcontractor",
    "rating": 5.0,
    "primary_contact_phone": "555-456-7899",
    "insurance_expiry_date": "2026-12-31",
    "compliance_expiry_date": "2026-06-30",
    "changes": {
      "rating": {
        "old": 4.8,
        "new": 5.0
      },
      "primary_contact_phone": {
        "old": "555-456-7890",
        "new": "555-456-7899"
      },
      "insurance_expiry_date": {
        "old": "2025-12-31",
        "new": "2026-12-31"
      }
    },
    "updated_at": "2025-03-10T09:45:00Z",
    "updated_by": "user_mno567"
  }
}
EVENT

vendor.deleted

Triggered when a vendor is deactivated or removed from the system. Includes basic vendor information and deletion metadata.

Payload Example

vendor.deleted
{
  "event_id": "evt_7e2a9c4b8f6d1a5c",
  "event_type": "vendor.deleted",
  "timestamp": "2025-04-05T13:20:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "vnd_234567",
    "vendor_name": "Elite Concrete Services",
    "deleted_at": "2025-04-05T13:20:00Z",
    "deleted_by": "user_pqr890"
  }
}

Time Off Events

Receive notifications when time off requests are created, updated, or deleted in your WorkSkedge account.

EVENT

time_off.created

Triggered when a new time off request is submitted. Includes employee details, dates, and request status.

Payload Example

time_off.created
{
  "event_id": "evt_to_1a2b3c4d5e6f",
  "event_type": "time_off.created",
  "timestamp": "2025-01-15T14:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "start_time": null,
    "end_time": null,
    "time_off_type": "vacation",
    "status": "approved",
    "reason": "Vacation",
    "notes": "Annual family vacation",
    "created_at": "2025-01-15T14:30:00Z"
  }
}
EVENT

time_off.updated

Triggered when a time off request is modified. Includes updated details and change information.

Payload Example

time_off.updated
{
  "event_id": "evt_to_7g8h9i0j1k2l",
  "event_type": "time_off.updated",
  "timestamp": "2025-01-18T09:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "time_off_type": "vacation",
    "status": "approved",
    "notes": "Extended by one day - manager approved",
    "updated_at": "2025-01-18T09:00:00Z",
    "changes": {
      "end_date": {"from": "2025-01-23", "to": "2025-01-24"},
      "notes": {"from": "Annual family vacation", "to": "Extended by one day - manager approved"}
    }
  }
}
EVENT

time_off.deleted

Triggered when a time off request is cancelled or removed. Includes basic request information.

Payload Example

time_off.deleted
{
  "event_id": "evt_to_3m4n5o6p7q8r",
  "event_type": "time_off.deleted",
  "timestamp": "2025-01-17T10:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "deleted_at": "2025-01-17T10:00:00Z"
  }
}

Customer & Location Events

Receive notifications when customers and locations are created, updated, or deleted.

EVENT

customer.created

Triggered when a new customer record is created in WorkSkedge. Includes complete customer details and contact information.

Payload Example

customer.created
{
  "event_id": "evt_6c8a9b3e1f7d4a2c",
  "event_type": "customer.created",
  "timestamp": "2025-01-08T11:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cust_789012",
    "name": "ABC Property Development",
    "type": "commercial",
    "email": "projects@abcproperty.com",
    "phone": "555-123-4567",
    "billing_address": {
      "address_line_1": "789 Business Blvd",
      "city": "San Francisco",
      "province": "CA",
      "postal_code": "94105",
      "country": "USA"
    },
    "created_at": "2025-01-08T11:30:00Z",
    "created_by": "user_bcd890"
  }
}
EVENT

customer.updated

Triggered when customer information is modified. Includes the complete current state of the customer record.

Payload Example

customer.updated
{
  "event_id": "evt_8d2f9b5c6e1a3b7d",
  "event_type": "customer.updated",
  "timestamp": "2025-01-10T09:45:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cust_789012",
    "name": "ABC Property Development LLC",
    "type": "commercial",
    "email": "contracts@abcproperty.com",
    "phone": "555-123-4567",
    "billing_address": {
      "address_line_1": "789 Business Blvd",
      "city": "San Francisco",
      "province": "CA",
      "postal_code": "94105",
      "country": "USA"
    },
    "updated_at": "2025-01-10T09:45:00Z",
    "updated_by": "user_xyz456"
  }
}
EVENT

customer.deleted

Triggered when a customer record is deleted from WorkSkedge. Includes customer identification and deletion metadata.

Payload Example

customer.deleted
{
  "event_id": "evt_3a7c5e9b2d4f8a1c",
  "event_type": "customer.deleted",
  "timestamp": "2025-01-12T16:20:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cust_789012",
    "name": "ABC Property Development LLC",
    "deleted_at": "2025-01-12T16:20:00Z"
  }
}
EVENT

location.created

Triggered when a new location is created for a customer. Includes complete location details and associated customer information.

Payload Example

location.created
{
  "event_id": "evt_9a3e7b2c4f8d1a5b",
  "event_type": "location.created",
  "timestamp": "2025-01-09T14:00:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "loc_345678",
    "customer_id": "cust_789012",
    "name": "Downtown Office Building",
    "address_line_1": "456 Construction Way",
    "address_line_2": "Suite 300",
    "city": "San Francisco",
    "province": "CA",
    "postal_code": "94102",
    "country": "USA",
    "coordinates": {
      "latitude": 37.7749,
      "longitude": -122.4194
    },
    "customer": {
      "id": "cust_789012",
      "name": "ABC Property Development"
    },
    "created_at": "2025-01-09T14:00:00Z",
    "created_by": "user_efg123"
  }
}
EVENT

location.updated

Triggered when location information is modified. Includes the complete current state of the location record.

Payload Example

location.updated
{
  "event_id": "evt_5c2e8a4b9d6f1a7c",
  "event_type": "location.updated",
  "timestamp": "2025-01-11T10:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "loc_345678",
    "customer_id": "cust_789012",
    "name": "Downtown Office Building - Tower A",
    "address_line_1": "456 Construction Way",
    "address_line_2": "Tower A, Suite 300",
    "city": "San Francisco",
    "province": "CA",
    "postal_code": "94102",
    "country": "USA",
    "coordinates": {
      "latitude": 37.7749,
      "longitude": -122.4194
    },
    "customer": {
      "id": "cust_789012",
      "name": "ABC Property Development LLC"
    },
    "updated_at": "2025-01-11T10:30:00Z",
    "updated_by": "user_xyz789"
  }
}
EVENT

location.deleted

Triggered when a location record is deleted from WorkSkedge. Includes location identification and deletion metadata.

Payload Example

location.deleted
{
  "event_id": "evt_7b4d9a6c2f5e8a3b",
  "event_type": "location.deleted",
  "timestamp": "2025-01-13T14:15:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "loc_345678",
    "customer_id": "cust_789012",
    "name": "Downtown Office Building - Tower A",
    "deleted_at": "2025-01-13T14:15:00Z"
  }
}

Timesheet Events

Receive notifications throughout the timesheet lifecycle - from creation and updates to submission, approval locks, and final payroll processing.

EVENT

timesheet.created

Triggered when a new daily timesheet is created for an employee. Includes timesheet date, status, and work entries with hours and associated work orders.

Payload Example

timesheet.created
{
  "event_id": "evt_timesheet_created_001",
  "event_type": "timesheet.created",
  "timestamp": "2025-01-15T08:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "emp_number": "EMP-001",
    "employee_name": "John Smith",
    "timesheet_date": "2025-01-15",
    "status": "draft",
    "entry_count": 0,
    "created_at": "2025-01-15T08:00:00Z"
  }
}
EVENT

timesheet.updated

Triggered when a daily timesheet entry is modified. The update_type field indicates the type of change: entry_added, entry_updated, entry_deleted, or notes_updated.

Payload Example

timesheet.updated
{
  "event_id": "evt_timesheet_updated_001",
  "event_type": "timesheet.updated",
  "timestamp": "2025-01-15T14:30:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "update_type": "entry_added",
    "updated_at": "2025-01-15T14:30:00Z",
    "entry": {
      "entry_id": "e1f2g3h4-i5j6-7890-klmn-op1234567890",
      "entry_date": "2025-01-15",
      "work_order_id": "wo_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
      "work_order_name": "Electrical Phase",
      "work_type_id": "wt_123e4567-e89b-12d3-a456-426614174000",
      "work_type_name": "Installation",
      "hours": 8.0,
      "notes": "Main panel installation"
    }
  }
}
EVENT

timesheet.submitted

Triggered when an employee submits their timesheet for approval. Includes complete timesheet data with all work entries and total hours.

Payload Example

timesheet.submitted
{
  "event_id": "evt_timesheet_submitted_001",
  "event_type": "timesheet.submitted",
  "timestamp": "2025-01-15T17:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "emp_number": "EMP-001",
    "employee_name": "John Smith",
    "timesheet_date": "2025-01-15",
    "total_hours": 8.5,
    "submitted_at": "2025-01-15T17:00:00Z",
    "entries": [
      {
        "entry_date": "2025-01-15",
        "work_order_id": "wo_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
        "work_order_name": "Electrical Phase",
        "hours": 8.0,
        "notes": "Main panel installation"
      },
      {
        "entry_date": "2025-01-15",
        "work_order_id": "wo_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
        "work_order_name": "Electrical Phase",
        "hours": 0.5,
        "notes": "Overtime"
      }
    ]
  }
}
EVENT

timesheet.locked

Triggered when a timesheet is locked by a manager or supervisor, preventing further edits. Includes who locked it and when.

Payload Example

timesheet.locked
{
  "event_id": "evt_timesheet_locked_001",
  "event_type": "timesheet.locked",
  "timestamp": "2025-01-15T10:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_date": "2025-01-15",
    "emp_number": "EMP-001",
    "employee_name": "John Smith",
    "total_hours": 8.5,
    "locked_at": "2025-01-15T10:00:00Z",
    "locked_by": "mgr_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
    "locked_by_name": "Manager Smith"
  }
}
EVENT

timesheet.unlocked

Triggered when a previously locked timesheet is unlocked by a manager, allowing edits again. Includes unlock authorization details.

Payload Example

timesheet.unlocked
{
  "event_id": "evt_timesheet_unlocked_001",
  "event_type": "timesheet.unlocked",
  "timestamp": "2025-01-16T09:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "emp_number": "EMP-001",
    "employee_name": "John Smith",
    "timesheet_date": "2025-01-15",
    "unlocked_by": "mgr_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
    "unlocked_by_name": "Manager Smith",
    "unlocked_at": "2025-01-16T09:00:00Z"
  }
}
EVENT

timesheet.processed

Triggered when a timesheet is marked as processed for payroll or billing. Indicates the timesheet has been approved and finalized.

Payload Example

timesheet.processed
{
  "event_id": "evt_timesheet_processed_001",
  "event_type": "timesheet.processed",
  "timestamp": "2025-01-16T16:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "emp_number": "EMP-001",
    "employee_name": "John Smith",
    "timesheet_date": "2025-01-15",
    "total_hours": 8.5,
    "processed_by": "mgr_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
    "processed_by_name": "Manager Smith",
    "processed_at": "2025-01-16T16:00:00Z"
  }
}
EVENT

timesheets.processed.batch

Triggered when multiple timesheets are processed together in a batch operation. Includes all processed timesheets and batch metadata.

Payload Example

timesheets.processed.batch
{
  "event_id": "evt_batch_processed_001",
  "event_type": "timesheets.processed.batch",
  "timestamp": "2025-01-19T16:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "timesheets": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567801",
        "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567801",
        "emp_number": "EMP-001",
        "employee_name": "John Smith",
        "timesheet_date": "2025-01-15",
        "total_hours": 8.5
      },
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567802",
        "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567802",
        "emp_number": "EMP-001",
        "employee_name": "John Smith",
        "timesheet_date": "2025-01-16",
        "total_hours": 7.0
      },
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567803",
        "timesheet_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567803",
        "emp_number": "EMP-001",
        "employee_name": "John Smith",
        "timesheet_date": "2025-01-17",
        "total_hours": 8.0
      }
    ],
    "processed_by": "mgr_987fcdeb-51a2-43f7-8c9d-0e1f2a3b4c5d",
    "processed_by_name": "Manager Smith",
    "processed_at": "2025-01-19T16:00:00Z",
    "total_count": 3
  }
}

Contact Events

Receive notifications when contacts are created, updated, or deleted in your WorkSkedge account.

EVENT

contact.created

Triggered when a new contact is created for a customer. Includes complete contact details and associated customer information.

Payload Example

contact.created
{
  "event_id": "evt_1d9c7a5b3e8f2a4c",
  "event_type": "contact.created",
  "timestamp": "2025-01-25T13:45:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cnt_567890",
    "customer_id": "cust_789012",
    "customer_name": "ABC Property Development",
    "first_name": "Lisa",
    "last_name": "Martinez",
    "email": "lisa.martinez@abcproperty.com",
    "phone": "555-567-8901",
    "role": "Operations Manager",
    "is_primary": false,
    "created_at": "2025-01-25T13:45:00Z",
    "created_by": "user_bcd012"
  }
}
EVENT

contact.updated

Triggered when contact information is modified. Includes the complete current state of the contact record.

Payload Example

contact.updated
{
  "event_id": "evt_6f3d8a5c2e9b4a1d",
  "event_type": "contact.updated",
  "timestamp": "2025-01-26T11:20:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cnt_567890",
    "customer_id": "cust_789012",
    "customer_name": "ABC Property Development LLC",
    "first_name": "Lisa",
    "last_name": "Martinez",
    "email": "l.martinez@abcproperty.com",
    "phone": "555-567-8901",
    "mobile": "555-890-1234",
    "title": "Senior Operations Manager",
    "is_primary": true,
    "updated_at": "2025-01-26T11:20:00Z",
    "updated_by": "user_xyz456"
  }
}
EVENT

contact.deleted

Triggered when a contact record is deleted from WorkSkedge. Includes contact identification and deletion metadata.

Payload Example

contact.deleted
{
  "event_id": "evt_9c4e7a2b6d5f8a3c",
  "event_type": "contact.deleted",
  "timestamp": "2025-01-28T15:30:00Z",
  "company_id": "comp_abc123",
  "data": {
    "id": "cnt_567890",
    "customer_id": "cust_789012",
    "first_name": "Lisa",
    "last_name": "Martinez",
    "deleted_at": "2025-01-28T15:30:00Z"
  }
}

Complete Webhook Handler Example

Here's a complete example of a webhook handler that processes different event types:

webhook-handler.js
const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.json());

function verifyWebhookSignature(payload, signature, secret, timestamp) {
  const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 300;
  if (timestamp < fiveMinutesAgo) {
    throw new Error('Webhook timestamp too old');
  }

  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

async function processWebhook(eventType, data, webhookId) {
  switch (eventType) {
    case 'work_order.created':
      console.log('New work order created:', data.work_order_number);
      break;

    case 'work_order.status_changed':
      console.log(`Work order ${data.work_order_number} status changed from ${data.previous_status} to ${data.new_status}`);
      break;

    case 'report.completion_submitted':
      console.log('Completion report submitted for:', data.work_order_number);
      break;

    case 'report.completion_approved':
      console.log('Completion report approved for:', data.work_order_number);
      break;

    case 'project.created':
      console.log('New project created:', data.project_name);
      break;

    case 'project.closed':
      console.log('Project closed:', data.project_name, 'at', data.closed_at);
      break;

    case 'employee.created':
      console.log('New employee added:', data.name);
      break;

    case 'employee.updated':
      console.log('Employee updated:', data.name);
      break;

    case 'vendor.created':
      console.log('New vendor added:', data.vendor_name);
      break;

    case 'timesheet.created':
      console.log(`Timesheet created for ${data.employee_name} on ${data.timesheet_date}`);
      break;

    case 'timesheet.updated':
      console.log(`Timesheet updated for ${data.employee_name} - status: ${data.status}`);
      break;

    case 'contact.created':
      console.log('New contact added:', `${data.first_name} ${data.last_name}`);
      break;

    default:
      console.log('Unhandled event type:', eventType);
  }
}

app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const webhookId = req.headers['x-webhook-id'];
  const eventType = req.headers['x-webhook-event'];
  const payload = JSON.stringify(req.body);

  try {
    if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    res.status(200).json({ received: true });

    await processWebhook(eventType, req.body.data, webhookId);
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(400).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
webhook_handler.py
import hmac
import hashlib
import time
import os
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')

def verify_webhook_signature(payload, signature, timestamp):
    five_minutes_ago = int(time.time()) - 300
    if int(timestamp) < five_minutes_ago:
        raise ValueError('Webhook timestamp too old')

    signed_payload = f"{timestamp}.{payload}"
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)

def process_webhook(event_type, data, webhook_id):
    if event_type == 'work_order.created':
        print(f"New work order created: {data.get('work_order_number')}")

    elif event_type == 'work_order.status_changed':
        wo_num = data.get('work_order_number')
        old = data.get('previous_status')
        new = data.get('new_status')
        print(f"Work order {wo_num} status changed from {old} to {new}")

    elif event_type == 'report.completion_submitted':
        print(f"Completion report submitted for: {data.get('work_order_number')}")

    elif event_type == 'report.completion_approved':
        print(f"Completion report approved for: {data.get('work_order_number')}")

    elif event_type == 'project.created':
        print(f"New project created: {data.get('project_name')}")

    elif event_type == 'project.closed':
        print(f"Project closed: {data.get('project_name')} at {data.get('closed_at')}")

    elif event_type == 'employee.created':
        print(f"New employee added: {data.get('name')}")

    elif event_type == 'employee.updated':
        print(f"Employee updated: {data.get('name')}")

    elif event_type == 'vendor.created':
        print(f"New vendor added: {data.get('vendor_name')}")

    elif event_type == 'timesheet.created':
        print(f"Timesheet created for {data.get('employee_name')} on {data.get('timesheet_date')}")

    elif event_type == 'timesheet.updated':
        print(f"Timesheet updated for {data.get('employee_name')} - status: {data.get('status')}")

    elif event_type == 'contact.created':
        print(f"New contact added: {data.get('first_name')} {data.get('last_name')}")

    else:
        print(f"Unhandled event type: {event_type}")

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    webhook_id = request.headers.get('X-Webhook-ID')
    event_type = request.headers.get('X-Webhook-Event')
    payload = request.get_data(as_text=True)

    try:
        if not verify_webhook_signature(payload, signature, timestamp):
            return jsonify({'error': 'Invalid signature'}), 401

        data = request.get_json()

        process_webhook(event_type, data.get('data'), webhook_id)

        return jsonify({'received': True}), 200
    except Exception as e:
        print(f'Webhook processing error: {str(e)}')
        return jsonify({'error': str(e)}), 400

if __name__ == '__main__':
    app.run(port=3000)

Retry Logic & Error Handling

If your endpoint fails to respond with a 2xx status code, WorkSkedge will automatically retry delivery using exponential backoff:

1st
Immediate

First delivery attempt

2nd
1 minute

After initial failure

3rd
5 minutes

After second failure

4th
15 minutes

After third failure

5th
1 hour

After fourth failure

6th
6 hours

Final attempt

Retry Behavior: 5xx errors (server errors) trigger automatic retries. 4xx errors (client errors) are considered permanent failures and are not retried. After all retry attempts are exhausted, the webhook delivery is marked as failed and you'll receive an alert. You can manually retry failed webhooks from the dashboard.

Best Practices

  • Always verify signatures to ensure webhooks are from WorkSkedge
  • Check timestamps to prevent replay attacks (reject requests older than 5 minutes)
  • Use HTTPS endpoints to encrypt data in transit
  • Store webhook secrets securely using environment variables or secret management systems
  • Respond quickly with a 200 status, then process webhook data asynchronously
  • Implement idempotency using the webhook ID to handle duplicate deliveries gracefully
  • Log webhook events for debugging and audit purposes
  • Monitor webhook deliveries in your WorkSkedge dashboard to catch issues early
  • Understand echo prevention: If you use inbound webhooks to send data to WorkSkedge, you won't receive outbound webhooks for those same changes - this prevents infinite loops
Echo Storm Prevention

WorkSkedge automatically prevents echo storms in bidirectional integrations. When your system sends data to WorkSkedge via inbound webhooks, your webhook endpoint is automatically excluded from receiving outbound notifications for those specific changes.

Example: If your ERP creates a project via an inbound webhook, you won't receive the project.created outbound webhook back. Other subscribed systems will still receive it normally. This allows safe bidirectional synchronization without manual loop detection.

Endpoint Requirements

Your webhook endpoint must meet these requirements:

  • Accept HTTP POST requests with JSON payloads
  • Respond within 30 seconds with appropriate status codes
  • Return 2xx status code (200-299) for successful receipt
  • Return 4xx for permanent errors (invalid data, not found, etc.)
  • Return 5xx for temporary errors (server issues, database down, etc.)
  • Use HTTPS (HTTP endpoints are not supported for security)
  • Handle duplicate deliveries gracefully using webhook IDs
  • Process webhooks asynchronously to avoid timeouts

Testing Your Webhook Endpoint

Use the WorkSkedge dashboard to test your webhook endpoint before going live:

  1. Navigate to Settings → Webhooks in your WorkSkedge dashboard
  2. Add your webhook endpoint URL
  3. Select the event types you want to receive
  4. Click "Send Test Webhook" to send a test event
  5. Verify your endpoint receives and processes the test webhook correctly
  6. Check the delivery logs to see the request and response details

Local Testing Tip: Use tools like ngrok or localtunnel to expose your local development server to the internet for testing webhooks during development.

Event Catalog

Browse all available webhook events in WorkSkedge. Subscribe to the events you need and receive real-time notifications when they occur.

Work Orders 6 events

Outbound workorder.created

Triggered when a new work order is created in WorkSkedge

Example Payload

{
  "event": "workorder.created",
  "timestamp": "2025-10-20T14:30:00Z",
  "data": {
    "id": "wo_xyz789",
    "workOrderNumber": "WO-12345",
    "title": "Install HVAC System",
    "status": "scheduled",
    "priority": "high",
    "projectId": "proj_abc123"
  }
}
Outbound workorder.updated

Triggered when a work order is updated (status, assignments, details, etc.)

Example Payload

{
  "event": "workorder.updated",
  "timestamp": "2025-10-20T16:45:00Z",
  "data": {
    "id": "wo_xyz789",
    "workOrderNumber": "WO-12345",
    "title": "Install HVAC System",
    "status": "in_progress",
    "priority": "urgent",
    "projectId": "proj_abc123",
    "assignedTo": ["emp_001", "emp_002"],
    "changes": {
      "status": {"from": "scheduled", "to": "in_progress"},
      "priority": {"from": "high", "to": "urgent"}
    }
  }
}
Outbound workorder.deleted

Triggered when a work order is deleted or archived

Example Payload

{
  "event": "workorder.deleted",
  "timestamp": "2025-10-20T18:00:00Z",
  "data": {
    "id": "wo_xyz789",
    "workOrderNumber": "WO-12345",
    "title": "Install HVAC System",
    "projectId": "proj_abc123",
    "deletedBy": "emp_manager_001",
    "reason": "Project cancelled by customer"
  }
}
Outbound workorder.started

Triggered when work begins on a work order

Outbound workorder.completed

Triggered when a work order is marked as completed

Outbound workorder.cancelled

Triggered when a work order is cancelled

Projects 4 events

Outbound project.created

Triggered when a new project is created

Example Payload

{
  "event": "project.created",
  "timestamp": "2025-10-20T10:00:00Z",
  "data": {
    "id": "proj_abc123",
    "projectNumber": "PRJ-2025-001",
    "name": "Downtown Office Renovation",
    "customer": "ABC Corporation",
    "address": "123 Main St, New York, NY",
    "status": "planning",
    "startDate": "2025-11-01",
    "estimatedEndDate": "2026-02-28"
  }
}
Outbound project.updated

Triggered when project details are updated

Example Payload

{
  "event": "project.updated",
  "timestamp": "2025-10-21T14:30:00Z",
  "data": {
    "id": "proj_abc123",
    "projectNumber": "PRJ-2025-001",
    "name": "Downtown Office Renovation",
    "status": "active",
    "changes": {
      "status": {"from": "planning", "to": "active"},
      "estimatedEndDate": {"from": "2026-02-28", "to": "2026-03-15"}
    }
  }
}
Outbound project.deleted

Triggered when a project is deleted or archived

Example Payload

{
  "event": "project.deleted",
  "timestamp": "2025-10-22T09:00:00Z",
  "data": {
    "id": "proj_abc123",
    "projectNumber": "PRJ-2025-001",
    "name": "Downtown Office Renovation",
    "deletedBy": "emp_manager_001",
    "reason": "Contract terminated"
  }
}
Outbound project.closed

Triggered when a project is closed or completed

Example Payload

{
  "event_id": "evt_abc123",
  "event_type": "project.closed",
  "timestamp": "2025-01-15T14:00:00Z",
  "company_id": "123e4567-e89b-12d3-a456-426614174000",
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "project_code": "PRJ-2025-100",
    "project_name": "Skyline Tower Construction",
    "closed_at": "2025-01-15T14:00:00Z"
  }
}

Employees 4 events

Outbound employee.created

Triggered when a new employee is added to the system

Example Payload

{
  "event": "employee.created",
  "timestamp": "2025-10-20T09:00:00Z",
  "data": {
    "id": "emp_12345",
    "emp_number": "EMP-12345",
    "name": "John Martinez",
    "email": "john.martinez@company.com",
    "phone": "+1-555-1234",
    "department": "Field Operations",
    "role": "Site Supervisor",
    "employee_type": "site_lead",
    "employee_skills": ["Concrete", "Framing", "Safety Management"],
    "start_date": "2025-10-20",
    "emergency_contact_name": "Maria Martinez",
    "emergency_contact_phone": "+1-555-5678"
  }
}
Outbound employee.updated

Triggered when employee information is updated

Example Payload

{
  "event": "employee.updated",
  "timestamp": "2025-10-21T11:30:00Z",
  "data": {
    "id": "emp_12345",
    "emp_number": "EMP-12345",
    "name": "John Martinez",
    "email": "john.martinez@company.com",
    "department": "Field Operations",
    "role": "Senior Site Supervisor",
    "employee_type": "project_owner",
    "employee_skills": ["Concrete", "Framing", "Safety Management", "Project Management"],
    "emergency_contact_name": "Maria Martinez",
    "emergency_contact_phone": "+1-555-9999",
    "changes": {
      "role": {"from": "Site Supervisor", "to": "Senior Site Supervisor"},
      "employee_type": {"from": "site_lead", "to": "project_owner"},
      "employee_skills": {"added": ["Project Management"], "removed": []}
    }
  }
}
Outbound employee.deleted

Triggered when an employee is removed from the system

Example Payload

{
  "event": "employee.deleted",
  "timestamp": "2025-10-22T15:00:00Z",
  "data": {
    "id": "emp_12345",
    "emp_number": "EMP-12345",
    "name": "John Martinez",
    "email": "john.martinez@company.com",
    "deleted_at": "2025-10-22T15:00:00Z"
  }
}
Outbound employee.assigned

Triggered when an employee is assigned to a work order or project

Vendors 3 events

Outbound vendor.created

Triggered when a new vendor is registered

Example Payload

{
  "event": "vendor.created",
  "timestamp": "2025-10-20T13:00:00Z",
  "data": {
    "id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "type": "subcontractor",
    "trade_specialty": "HVAC",
    "primary_contact_name": "Robert Williams",
    "primary_contact_email": "robert@superiorhvac.com",
    "primary_contact_phone": "+1-555-HVAC-100",
    "rating": 5,
    "insurance_expiry_date": "2025-12-31",
    "compliance_expiry_date": "2025-06-30",
    "regions": ["Midwest", "Great Lakes"]
  }
}
Outbound vendor.updated

Triggered when vendor information is updated

Example Payload

{
  "event": "vendor.updated",
  "timestamp": "2025-10-21T16:00:00Z",
  "data": {
    "id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "type": "subcontractor",
    "rating": 5,
    "insurance_expiry_date": "2026-12-31",
    "changes": {
      "rating": {"from": 4, "to": 5},
      "insurance_expiry_date": {"from": "2025-12-31", "to": "2026-12-31"},
      "regions": {"added": ["Northeast"], "removed": []}
    }
  }
}
Outbound vendor.deleted

Triggered when a vendor is removed

Example Payload

{
  "event": "vendor.deleted",
  "timestamp": "2025-10-22T10:30:00Z",
  "data": {
    "id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "deleted_at": "2025-10-22T10:30:00Z"
  }
}

Time Off 3 events

Outbound time_off.created

Triggered when an employee submits a time off request

Example Payload

{
  "event": "time_off.created",
  "timestamp": "2025-01-15T14:30:00Z",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "time_off_type": "vacation",
    "status": "approved",
    "reason": "Vacation",
    "notes": "Annual family vacation"
  }
}
Outbound time_off.updated

Triggered when a time off request is modified

Example Payload

{
  "event": "time_off.updated",
  "timestamp": "2025-01-18T09:00:00Z",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "time_off_type": "vacation",
    "status": "approved",
    "notes": "Extended by one day",
    "changes": {
      "end_date": {"from": "2025-01-23", "to": "2025-01-24"}
    }
  }
}
Outbound time_off.deleted

Triggered when a time off request is cancelled or removed

Example Payload

{
  "event": "time_off.deleted",
  "timestamp": "2025-01-17T10:00:00Z",
  "data": {
    "id": "to_123456",
    "emp_number": "EMP-12345",
    "employee_name": "John Martinez",
    "start_date": "2025-01-20",
    "end_date": "2025-01-24",
    "deleted_at": "2025-01-17T10:00:00Z"
  }
}

Timesheets 3 events

Outbound timesheet.created

Triggered when a daily timesheet is created for an employee

Example Payload

{
  "event": "timesheet.created",
  "timestamp": "2025-10-20T18:00:00Z",
  "data": {
    "id": "timesheet_001",
    "emp_number": "EMP-1001",
    "timesheet_date": "2025-10-03",
    "status": "draft",
    "total_hours": 8.5,
    "entries": [
      {
        "entry_date": "2025-10-03",
        "work_order_number": "WO-00012",
        "hours": 5.0,
        "start_time": "08:00",
        "end_time": "13:00",
        "work_type_code": "REG",
        "work_type_name": "Regular Time",
        "notes": "HVAC installation - morning shift"
      },
      {
        "entry_date": "2025-10-03",
        "work_order_number": "WO-00009",
        "hours": 3.5,
        "start_time": "13:30",
        "end_time": "17:00",
        "work_type_code": "REG",
        "work_type_name": "Regular Time",
        "notes": "Electrical work - panel installation"
      }
    ]
  }
}
Outbound timesheet.updated

Triggered when a daily timesheet is updated (status change, hours adjustment, or entries added)

Example Payload

{
  "event": "timesheet.updated",
  "timestamp": "2025-10-21T09:30:00Z",
  "data": {
    "id": "timesheet_001",
    "emp_number": "EMP-1001",
    "timesheet_date": "2025-10-03",
    "status": "submitted",
    "total_hours": 8.5,
    "notes": "Day completed. All work orders documented.",
    "changes": {
      "status": {"from": "draft", "to": "submitted"},
      "total_hours": {"from": 8.5, "to": 8.5}
    }
  }
}
Outbound timesheet.deleted

Triggered when a daily timesheet is deleted

Example Payload

{
  "event": "timesheet.deleted",
  "timestamp": "2025-10-21T10:00:00Z",
  "data": {
    "id": "timesheet_001",
    "emp_number": "EMP-1001",
    "week_start": "2025-10-03",
    "week_end": "2025-10-09",
    "deleted_by": "emp_supervisor_001",
    "reason": "Duplicate timesheet for this week"
  }
}

Reports 3 events

Outbound report.generated

Triggered when a scheduled report is generated

Outbound report.scheduled

Triggered when a new report schedule is created

Outbound report.failed

Triggered when report generation fails

Purchase Orders 8 events

Outbound po.created

Triggered when a new purchase order is created

Example Payload

{
  "event": "po.created",
  "timestamp": "2025-10-20T10:15:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "project_name": "Downtown Office Renovation",
    "status": "draft",
    "created_by": "emp_manager_001",
    "created_by_name": "Sarah Johnson",
    "not_to_exceed_amount": 25000.00,
    "currency": "USD",
    "line_items": [
      {
        "id": "line_001",
        "description": "HVAC Unit - Model XYZ-5000",
        "quantity": 2,
        "unit_price": 5500.00,
        "total": 11000.00,
        "category": "Equipment"
      },
      {
        "id": "line_002",
        "description": "Installation Labor",
        "quantity": 40,
        "unit_price": 85.00,
        "total": 3400.00,
        "category": "Labor"
      }
    ],
    "subtotal": 14400.00,
    "tax": 1152.00,
    "total_amount": 15552.00,
    "notes": "Delivery required by Nov 15, 2025",
    "delivery_date": "2025-11-15",
    "payment_terms": "Net 30"
  }
}
Outbound po.updated

Triggered when purchase order details are modified

Example Payload

{
  "event": "po.updated",
  "timestamp": "2025-10-20T14:30:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "status": "draft",
    "not_to_exceed_amount": 28000.00,
    "total_amount": 17952.00,
    "updated_by": "emp_manager_001",
    "updated_by_name": "Sarah Johnson",
    "changes": {
      "not_to_exceed_amount": {"from": 25000.00, "to": 28000.00},
      "line_items": {
        "added": [
          {
            "id": "line_003",
            "description": "Additional Ductwork",
            "quantity": 30,
            "unit_price": 80.00,
            "total": 2400.00
          }
        ],
        "removed": [],
        "modified": []
      }
    }
  }
}
Outbound po.submitted

Triggered when a purchase order is submitted for approval

Example Payload

{
  "event": "po.submitted",
  "timestamp": "2025-10-20T15:00:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "project_name": "Downtown Office Renovation",
    "status": "pending_approval",
    "total_amount": 17952.00,
    "not_to_exceed_amount": 28000.00,
    "submitted_by": "emp_manager_001",
    "submitted_by_name": "Sarah Johnson",
    "submitted_at": "2025-10-20T15:00:00Z",
    "approval_workflow": {
      "required_approvers": [
        {
          "id": "emp_director_001",
          "name": "Michael Chen",
          "role": "Director",
          "approval_threshold": 25000.00
        }
      ],
      "approval_deadline": "2025-10-22T17:00:00Z"
    }
  }
}
Outbound po.approved

Triggered when a purchase order is approved

Example Payload

{
  "event": "po.approved",
  "timestamp": "2025-10-21T09:15:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "project_name": "Downtown Office Renovation",
    "status": "approved",
    "total_amount": 17952.00,
    "not_to_exceed_amount": 28000.00,
    "approved_by": "emp_director_001",
    "approved_by_name": "Michael Chen",
    "approved_at": "2025-10-21T09:15:00Z",
    "approval_notes": "Approved for immediate ordering. Delivery timeline is critical.",
    "approval_workflow": {
      "approvers": [
        {
          "id": "emp_director_001",
          "name": "Michael Chen",
          "role": "Director",
          "approved_at": "2025-10-21T09:15:00Z",
          "decision": "approved"
        }
      ]
    }
  }
}
Outbound po.rejected

Triggered when a purchase order is rejected

Example Payload

{
  "event": "po.rejected",
  "timestamp": "2025-10-21T10:30:00Z",
  "data": {
    "id": "po_456def",
    "po_number": "PO-2025-1235",
    "vendor_id": "vendor_002",
    "vendor_name": "BuildPro Supplies",
    "project_id": "proj_abc124",
    "project_name": "Harbor View Apartments",
    "status": "rejected",
    "total_amount": 45000.00,
    "not_to_exceed_amount": 40000.00,
    "rejected_by": "emp_director_001",
    "rejected_by_name": "Michael Chen",
    "rejected_at": "2025-10-21T10:30:00Z",
    "rejection_reason": "Total amount exceeds approved budget threshold. Please revise line items or request budget increase.",
    "approval_workflow": {
      "approvers": [
        {
          "id": "emp_director_001",
          "name": "Michael Chen",
          "role": "Director",
          "rejected_at": "2025-10-21T10:30:00Z",
          "decision": "rejected"
        }
      ]
    }
  }
}
Outbound po.received

Triggered when goods or services from a purchase order are received

Example Payload

{
  "event": "po.received",
  "timestamp": "2025-11-15T13:45:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "project_name": "Downtown Office Renovation",
    "status": "partially_received",
    "total_amount": 17952.00,
    "received_by": "emp_site_lead_003",
    "received_by_name": "Carlos Rodriguez",
    "received_at": "2025-11-15T13:45:00Z",
    "receipt_location": "Project Site - Building A",
    "received_items": [
      {
        "line_item_id": "line_001",
        "description": "HVAC Unit - Model XYZ-5000",
        "quantity_ordered": 2,
        "quantity_received": 2,
        "condition": "good",
        "notes": "Units inspected and in excellent condition"
      },
      {
        "line_item_id": "line_002",
        "description": "Installation Labor",
        "quantity_ordered": 40,
        "quantity_received": 0,
        "condition": "pending",
        "notes": "Labor scheduled for next week"
      }
    ],
    "receipt_notes": "Equipment delivered on time. All packaging intact."
  }
}
Outbound po.attachment.added

Triggered when a document or file is attached to a purchase order

Example Payload

{
  "event": "po.attachment.added",
  "timestamp": "2025-10-21T11:00:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "attachment": {
      "id": "att_001xyz",
      "filename": "vendor_quote_hvac_system.pdf",
      "file_size": 245760,
      "file_type": "application/pdf",
      "category": "quote",
      "uploaded_by": "emp_manager_001",
      "uploaded_by_name": "Sarah Johnson",
      "uploaded_at": "2025-10-21T11:00:00Z",
      "download_url": "https://app.workskedge.com/api/v1/purchase-orders/po_789xyz/attachments/att_001xyz",
      "notes": "Original vendor quote for equipment"
    }
  }
}
Outbound po.attachment.removed

Triggered when an attachment is removed from a purchase order

Example Payload

{
  "event": "po.attachment.removed",
  "timestamp": "2025-10-22T09:30:00Z",
  "data": {
    "id": "po_789xyz",
    "po_number": "PO-2025-1234",
    "vendor_id": "vendor_001",
    "vendor_name": "Superior HVAC Solutions",
    "project_id": "proj_abc123",
    "attachment": {
      "id": "att_002xyz",
      "filename": "draft_quote_v1.pdf",
      "file_type": "application/pdf",
      "removed_by": "emp_manager_001",
      "removed_by_name": "Sarah Johnson",
      "removed_at": "2025-10-22T09:30:00Z",
      "removal_reason": "Replaced with final approved quote"
    }
  }
}