> For the complete documentation index, see [llms.txt](https://developer.usemultiplier.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.usemultiplier.com/webhook-integration.md).

# Webhook Integration

The Webhook integration allows partners to receive real-time notifications about various changes happening within the Multiplier platform.

### Initial Setup

To start receiving events, you must provide your webhook endpoint URL to the Multiplier team.

**Example Endpoint:** `https://xyz.partner-name.com/webhook`

Once configured, Multiplier will fire events to this URL via **POST** requests. You can filter these events on your end to process only the specific event types relevant to your integration.

***

### Webhook Content Format

Multiplier sends data with the header `Content-Type: application/json`. However, for security purposes, the payload is **signed**.

You will not receive a raw JSON object immediately. Instead, you will receive a **JWS (JSON Web Signature)** string.

> **Tip:** To inspect the payload content during development (without verifying the signature), you can paste the token string into tools like [jwt.io](https://jwt.io/).

***

### Validating the Signature

To ensure the webhook data was genuinely sent by Multiplier and has not been tampered with, you **must** verify the signature of the received JWS data.

#### 1. Fetch Public Keys (JWKS)

You need to fetch the Multiplier public key set (JWKS) to match the private key used for signing.

**Endpoint:** `GET https://api.usemultiplier.com/v1/partner/.well-known/jwks.json`

**Sample JWKS Response:**

```json
{
  "keys" : [ {
    "kty" : "EC",
    "crv" : "P-521",
    "kid" : "strategic-partner-service-key",
    "x" : "ATGYQ3Swxio2PULu8cHU5m0DrEodcPHplzBDZLsG9NiiiWuJDsU1giwRISaMTJxtJThw8vhLDNQSK0mVXjRX947l",
    "y" : "AItDCJC1319jdVh86cVJLSu98HvLOPJdH23WScSMOsivkHl5EdUv7PMjH3OF7-dDbmZN-LuWM86aXh7JHMWGZGww",
    "alg" : "ES512"
  } ]
}
```

> ⚠️ Key Rotation Warning: The keys in this key-set can be rotated without prior communication. You may cache the JWKS response for up to 1 hour. After that, you must re-fetch the set to ensure you have the latest keys. As long as *any one key* in the set matches the JWS, the request is authorized.

#### 2. Verify Logic (Sample Implementation)

Below is a sample implementation in Kotlin using the `nimbusds` library to verify and parse the payload.

```kotlin
import com.nimbusds.jose.JWSObject
import com.nimbusds.jose.jwk.ECKey
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.crypto.ECDSAVerifier
import com.fasterxml.jackson.databind.ObjectMapper

// 1. Parse the JWKS (Public Keys)
val jwkSet = JWKSet.parse(jwksStringResponse)

// 2. Parse the received raw JWS string
val jwsObject = JWSObject.parse(signedPayload)

// 3. Retrieve the correct public key using the Key ID (kid) from the header
val publicKey = jwkSet.getKeyByKeyId(jwsObject.header.keyID) as ECKey

// 4. Initialize the Verifier
val verifier = ECDSAVerifier(publicKey)

// 5. Verify the payload. This is the vital step!
if (!jwsObject.verify(verifier)) {
    throw RuntimeException("Verification failed!")
}

// 6. Read the Data
val payloadJson = objectMapper.readTree(jwsObject.payload.toString())
val eventType = payloadJson.get("eventType").asText()
val customerId = payloadJson.get("data").get("customerId").asText()
```

***

### Event Reference

#### Generic Webhook Payload

All webhook events follow a standard JSON format with three core fields: `eventType`, `data`, and `timestamp`.

```json
{
  "eventType": "String",
  "data": {
	  // common standard
    "id": "UUID",
    "customerId": "UUID",
    "employeeCode": "string",
    "status": "ACTIVE", // Possible values: ONBOARDING, ACTIVE, OFFBOARDING, ENDED, DELETED
    "type": "EMPLOYEE", // Options: "EMPLOYEE", "HR_MEMBER", "FREELANCER", "CONTRACTOR"
    "country": "USA" // ISO 3166-1 alpha-3
    // details - other fields in json format
  },
  "timestamp": "YYYY-MM-DDThh:mm:ss.sssZ" // ISO 8601
}
```

#### Fields <a href="#fields-272" id="fields-272"></a>

**`eventType` (required)**

The type of event being triggered. Supported event types:

* `EMPLOYEE_CREATED` - Triggered when a new employee is created
* `EMPLOYEE_ACTIVATED` - Triggered when an employee is activated
* `EMPLOYEE_UPDATED` - Triggered when employee information is modified
* `EMPLOYEE_DEACTIVATED` - Triggered when an employee is deactivated
* `EMPLOYEE_DELETED` - Triggered when an employee is deleted

**`timestamp` (required)**

ISO-8601 formatted timestamp indicating when the event occurred.

**Example**: `2025-08-07T14:30:00.000Z`&#x20;

**`data` (required)**

The event payload containing:

* **Common fields**: Standard metadata identifying the affected record (e.g., `employeeCode`, `companyId`)
* **Event-specific fields** *(optional)*: Additional details relevant to the specific event type

The structure of the `data` field varies by event type. See individual event schemas below.

#### `EMPLOYEE_CREATED`&#x20;

Fired when an employee is created.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "employeeCode": "string",
  "status": "ONBOARDING",
  "type": "EMPLOYEE",
  "country": "USA",
}
```

#### `EMPLOYEE_ACTIVATED`

Fired when an employee is moved to the `ACTIVE` state.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "employeeCode": "string",
  "status": "ACTIVE",
  "type": "EMPLOYEE",
  "country": "USA",
}
```

#### `EMPLOYEE_UPDATED`

Fired when an employee data is updated.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "employeeCode": "string",
  "status": "ACTIVE",
  "type": "EMPLOYEE",
  "country": "USA",
  // details
  "updateType": "PERSONAL_DETAILS", // Options: PERSONAL_DETAILS, ADDRESS_DETAILS, CONTACT_DETAILS, BANK_DETAILS, COMPENSATION_DETAILS, EDUCATION_DETAILS, EMPLOYMENT_DETAILS
}
```

#### `EMPLOYEE_DEACTIVATED`

Fired when an employee is moved to the `ENDED` state.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "employeeCode": "string",
  "status": "ENDED",
  "type": "EMPLOYEE",
  "country": "USA",
}
```

#### `EMPLOYEE_DELETED`

Fired when an employee is being deleted.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "employeeCode": "string",
  "status": "DELETED",
  "type": "EMPLOYEE",
  "country": "USA",
}
```

#### Approval webhook events

Approval request events are sent whenever the status of an approval request changes. Supported event types:

| Event type                  | Description                                    |
| --------------------------- | ---------------------------------------------- |
| `APPROVAL_REQUEST_CREATED`  | Sent when a new approval request is submitted. |
| `APPROVAL_REQUEST_APPROVED` | Sent when an approval request is approved.     |
| `APPROVAL_REQUEST_REJECTED` | Sent when an approval request is rejected.     |

Payload Data Schema:

```json
{
  "customerId": "UUID",
  "employeeCode": "string",
  "country": "CAN",
  "scope": "TIME_OFF",
  "status": "PENDING",
  "employeeId": "UUID",
  "approverId": "UUID",
  "requestedBy": {
    "id": "UUID",
    "type": "EMPLOYEE"
  },
  "requestedAt": "YYYY-MM-DDThh:mm:ss.sssZ",
  "resource": {
    "type": "TIME_OFF",
    "id": "UUID",
    "details": {
      "leaveType": "string",
      "startDate": "YYYY-MM-DD",
      "endDate": "YYYY-MM-DD",
      "numberOfDays": number,
      "description": "string"
    }
  }
}
```

**Approval-related event fields**

**`customerId` (required)**

UUID of the customer (company) the approval request belongs to.

**`employeeCode` (required)**

The external employee identifier you provided when onboarding the employee. Use this to reconcile with records in your system.

**`country` (required)**

The employee's work country as an ISO 3166-1 alpha-3 code (e.g. `USA`, `CAN`, `GBR`).

**`scope` (required)**

The category of the request. Supported values:

* `TIME_OFF`&#x20;

**`status` (required)**

The current status of the approval request. Supported values:

* `PENDING`
* `APPROVED`
* `REJECTED`

**`approverId` (required)**

UUID of the customer user responsible for reviewing this request.

**`requestedBy` (required)**

Identifies the user who submitted the request. Because admins can submit on behalf of an employee, the type may vary.

<table><thead><tr><th width="170.2421875">Sub-field</th><th width="110.40625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>UUID</td><td>UUID of the user who submitted the request.</td></tr><tr><td><code>type</code></td><td>enum</td><td><code>EMPLOYEE</code> if the employee submitted it themselves; <code>COMPANY_USER</code> if a customer user (e.g. an admin) submitted on their behalf.</td></tr></tbody></table>

**`requestedAt` (required)**

ISO-8601 UTC timestamp of when the request was submitted.

**`resource` (required)**

The resource the request is for.

<table><thead><tr><th width="170.24609375">Sub-field</th><th width="109.8515625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td>enum</td><td>The resource type. Currently equal to <code>scope</code>. Supported values: <code>TIME_OFF</code>.</td></tr><tr><td><code>id</code></td><td>UUID</td><td>UUID of the resource.</td></tr><tr><td><code>details</code></td><td>object</td><td>Resource-specific fields. See Resource details.</td></tr></tbody></table>

**`approvedBy` (conditional)**

The UUID of the customer user who approves the request.

**`approvedAt` (conditional)**

ISO-8601 UTC timestamp of when the request was approved. Present on `APPROVAL_REQUEST_APPROVED` events; absent otherwise.

**`rejectedBy` (conditional)**

The UUID of the customer user who rejects the request.

**`rejectedAt` (conditional)**

ISO-8601 UTC timestamp of when the request was rejected. Present on `APPROVAL_REQUEST_REJECTED` events; absent otherwise.

**Resource details**

The shape of `resource.details` depends on `resource.type`.

**`TIME_OFF`**

<table><thead><tr><th width="169.77734375">Field</th><th width="180.1328125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>leaveType</code></td><td>string</td><td>The leave type, e.g. <code>sick</code>, <code>annual</code>.</td></tr><tr><td><code>startDate</code></td><td>date (<code>YYYY-MM-DD</code>)</td><td>First day of leave.</td></tr><tr><td><code>endDate</code></td><td>date (<code>YYYY-MM-DD</code>)</td><td>Last day of leave (inclusive).</td></tr><tr><td><code>numberOfDays</code></td><td>number</td><td>Total days of leave. Fractional values are supported (e.g. <code>0.5</code> for a half day).</td></tr><tr><td> <code>description</code></td><td>string</td><td>Reason or note provided by the requester. May be empty.</td></tr></tbody></table>

#### `(deprecated) CONTRACT_CREATED`&#x20;

Fired when an employee is created.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "status": "ACTIVE",
  "type": "EMPLOYEE",
  "country": "USA"
}
```

#### `(deprecated) CONTRACT_ACTIVATED`

Fired when an employee is moved to the `ACTIVE` state.

Payload Data Schema:

```json
{
  "id": "UUID",
  "customerId": "UUID",
  "status": "ACTIVE",
  "type": "EMPLOYEE",
  "country": "USA"
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.usemultiplier.com/webhook-integration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
