# Using Webhooks for Slack Notifications

Webhooks are a way for services to send real-time HTTP callbacks to your application when certain events occur. Instead of polling an API repeatedly, your app can **subscribe** to updates and receive an HTTP POST as soon as something changes. This is perfect for building event-driven integrations: for example, Peoplelogic can notify your app immediately when an OKR (or any other entity like an IDP or Meeting) is marked **COMPLETED**, and you can handle it right away—say, by sending a Slack message. Let's dive in!

### Gradle Project Setup

Let’s set up a Kotlin/Gradle project with the dependencies you need. In your `build.gradle.kts`, include Ktor for both server and client, plus kotlinx.serialization:

```kotlin
plugins {
    kotlin("jvm") version "1.8.21"
    application
}

repositories {
    mavenCentral()
}

dependencies {
    // Ktor server
    implementation("io.ktor:ktor-server-core:2.3.1")
    implementation("io.ktor:ktor-server-netty:2.3.1")
    implementation("io.ktor:ktor-server-content-negotiation:2.3.1")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.1")

    // Ktor client (for Slack integration)
    implementation("io.ktor:ktor-client-cio:2.3.1")
    implementation("io.ktor:ktor-client-content-negotiation:2.3.1")
    implementation("io.ktor:ktor-client-logging:2.3.1")

    // Kotlinx JSON
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}

application {
    mainClass.set("MainKt")
}
```

This setup gives you everything you need to both **serve** webhook callbacks and **call** external HTTP endpoints (like Slack) within the same application.

***

### 1. Create a Webhook Signing Key

First, create a new signing key in Peoplelogic. This key is used to sign all webhook payloads.

```bash
curl -X POST "https://api.peoplelogic.dev/api/v1/webhook-key"      
-H "Authorization: Bearer <YOUR_API_TOKEN>"      
-H "Content-Type: application/json"      
-d '{"name":"MySigningKey","default":true}'
```

**Sample response**:

```json
{
  "id": "f6a1c9b2-0000-1111-2222-aaaaaaaaaaaa",
  "name": "MySigningKey",
  "secretBase64": "YWJjMTIzIT8kKiYoKSctPUB+",
  "default": true,
  "createdAt": "2025-05-28T14:21:00Z"
}
```

> **Note:** Keep the `secretBase64` value secure. You’ll use it in your listener to verify incoming webhook signatures.

***

### 2. Register a Webhook for Objective Status Changes

Now, register the webhook endpoint in Peoplelogic:

{% code overflow="wrap" %}

```bash
curl -X POST "https://api.peoplelogic.dev/api/v1/webhook"      -H "Authorization: Bearer <YOUR_API_TOKEN>"      
-H "Content-Type: application/json"      
-d '{
           "eventType": "objective.patch",
           "ifUpdates": ["status"],
           "url": "https://your-server.com/webhooks/peoplelogic",
           "signingKey": "f6a1c9b2-0000-1111-2222-aaaaaaaaaaaa"
         }'
```

{% endcode %}

In this request:

* `eventType` is the Peoplelogic event (format is `"<entityType>.<changeType>"`, e.g. `objective.patch` for updates). Using `"objective.patch"` with `ifUpdates: ["status"]` means “notify me when any objective is patched and its status field changed.” This filtering behavior is handled by Peoplelogic’s webhook triage (see [GitHub](https://github.com/peoplelogic/peoplelogic-apis/blob/6896b9ced5660a902aebcb8f05caca0e4e4d491f/modules/jobs/webhooks/src/main/kotlin/ai/peoplelogic/webhooks/WebhookTriageMessagesHandler.kt#L43-L54)).
* `url` is your public webhook endpoint.
* `ifUpdates` specifies which fields must be present in the change for the webhook to fire.
* `signingKey` is the ID of the key created above.

**Sample response**:

```json
{
  "id": "a9b8c7d6-3333-4444-5555-bbbbbbbbbbbb",
  "eventType": "objective.patch",
  "ifUpdates": ["status"],
  "url": "https://your-server.com/webhooks/peoplelogic",
  "signingKey": "f6a1c9b2-0000-1111-2222-aaaaaaaaaaaa",
  "active": true,
  "createdAt": "2025-05-28T14:22:00Z"
}
```

> This webhook fires whenever an objective’s `status` changes, thanks to the `ifUpdates` filter.

***

### 3. Create and Complete an OKR Objective

Next, create a demo OKR objective:

{% code overflow="wrap" %}

```bash
curl -X POST "https://api.peoplelogic.dev/api/v1/objective"      
-H "Authorization: Bearer <YOUR_API_TOKEN>"      
-H "Content-Type: application/json"      
-d '{
           "name": "Improve Customer Satisfaction",
           "description": "Boost our NPS score",
           "startingValue": 0,
           "targetValue": 100
         }'
```

{% endcode %}

> *You may need to adjust fields such as dates or cycle IDs depending on your Peoplelogic org configuration.*&#x20;

**Sample response**:

```json
{
  "id": "d9e8f7a6-6666-7777-8888-cccccccccccc",
  "name": "Improve Customer Satisfaction",
  "description": "Boost our NPS score",
  "status": { "current": "ACTIVE", "changedAt": null },
  "active": true
}
```

Save the `id` from the response—let’s call it `OBJ_ID`.

Now **complete** the objective:

{% code overflow="wrap" %}

```bash
curl -X PATCH "https://api.peoplelogic.dev/api/v1/objective/OBJ_ID"      
-H "Authorization: Bearer <YOUR_API_TOKEN>"      
-H "Content-Type: application/json"      
-d '{"status":"COMPLETED"}'
```

{% endcode %}

**Sample response**:

```json
{
  "id": "d9e8f7a6-6666-7777-8888-cccccccccccc",
  "status": { "current": "COMPLETED", "changedAt": "2025-05-28T14:25:00Z" },
  "active": true
}
```

When you issue the PATCH, Peoplelogic sees the `status` field changed and publishes an `EntityEvent` of type `"objective.patch"`. Since your webhook is listening for `"objective.patch"` with `status` in `ifUpdates`, Peoplelogic will send a POST to your Ktor server’s `/webhooks/peoplelogic` endpoint.

***

### Example API Output Summary

Putting it all together, you'll get something similar to the following:

```bash
# 1. Create Signing Key
curl -X POST "https://api.peoplelogic.dev/api/v1/webhook-key" 
-d '{"name":"MyKey"}'
# (Response) 
{
  "id": "f6a1c9b2-...-1234",
  "name": "MyKey",
  "secretBase64": "YWJjMTIzIT8kKiYoKSctPUB+",
  "default": true,
  "createdAt": "2025-05-28T14:21:00Z"
}

# 2. Register Webhook
curl -X POST "https://api.peoplelogic.dev/api/v1/webhook" -d '{
  "eventType":"objective.patch","url":"https://myapp.com/webhooks/peoplelogic",
  "ifUpdates":["status"],"signingKey":"f6a1c9b2-...-1234"
}'
# (Response)
{
  "id": "a9b8c7d6-...-4321",
  "eventType": "objective.patch",
  "url": "https://myapp.com/webhooks/peoplelogic",
  "ifUpdates": ["status"],
  "signingKey": "f6a1c9b2-...-1234",
  "active": true,
  "createdAt": "2025-05-28T14:22:00Z"
}

# 3. Create Objective
curl -X POST "https://api.peoplelogic.dev/api/v1/objective" -d '{
  "name": "Improve CSAT","description": "Boost score","targetValue":100,"startingValue":0
}'
# (Response)
{
  "id": "d9e8f7a6-...-7890",
  "name": "Improve CSAT",
  "description": "Boost score",
  "status": {"current": "ACTIVE", ...},
  "active": true,
  ...
}

# 4. Complete Objective
curl -X PATCH "https://api.peoplelogic.dev/api/v1/objective/d9e8f7a6-...-7890" 
-d '{"status":"COMPLETED"}'
# (Response)
{
  "id": "d9e8f7a6-...-7890",
  "status": {"current": "COMPLETED", "changedAt": "2025-05-28T14:25:00Z", ...},
  ...
}
```

***

### 4. Ktor Webhook Listener

Finally, let’s set up a Ktor server to receive and handle the webhook:

```kotlin
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.http.*
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

fun main() {
    val secretBase64 = "YWJjMTIzIT8kKiYoKSctPUB+"  // from step 1
    val slackWebhookUrl = "<YOUR_SLACK_WEBHOOK_URL>"

    embeddedServer(Netty, port = 8080) {
        routing {
            post("/webhooks/peoplelogic") {
                val payload = call.receiveText()
                val signature = call.request.headers["X-Signature"] ?: ""

                // Verify HMAC-SHA256 signature
                val secretBytes = Base64.getDecoder().decode(secretBase64)
                val hmac = Mac.getInstance("HmacSHA256").apply {
                    init(SecretKeySpec(secretBytes, "HmacSHA256"))
                }.doFinal(payload.toByteArray())
                val expected = Base64.getEncoder().encodeToString(hmac)
                if (signature != expected) {
                    call.respond(HttpStatusCode.Unauthorized, "Invalid signature")
                    return@post
                }

                // Parse payload
                val json = Json.parseToJsonElement(payload).jsonObject
                val objId = json["id"]?.jsonPrimitive?.content ?: "unknown"
                val status = json["status"]?.jsonObject
                              ?.get("current")?.jsonPrimitive?.content

                // Notify Slack if completed
                if (status == "COMPLETED") {
                    val client = HttpClient {
                        install(ContentNegotiation) { json() }
                    }
                    client.post(slackWebhookUrl) {
                        contentType(ContentType.Application.Json)
                        setBody(JsonObject(mapOf(
                            "text" to JsonPrimitive("✅ Objective $objId completed!")
                        )))
                    }
                }
                call.respondText("OK")
            }
        }
    }.start(wait = true)
}
```

Point your Peoplelogic webhook at `https://your-server.com/webhooks/peoplelogic`, and you’re all set. When an objective is completed, Peoplelogic will call your endpoint, you’ll verify the signature, and your app will post a Slack message. Happy coding!


---

# Agent Instructions: 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:

```
GET https://docs.peoplelogic.dev/tutorials/using-webhooks-for-slack-notifications.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
