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:
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.
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:
{
"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:
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"
}'
In this request:
eventType
is the Peoplelogic event (format is"<entityType>.<changeType>"
, e.g.objective.patch
for updates). Using"objective.patch"
withifUpdates: ["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).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:
{
"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 theifUpdates
filter.
3. Create and Complete an OKR Objective
Next, create a demo OKR objective:
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
}'
You may need to adjust fields such as dates or cycle IDs depending on your Peoplelogic org configuration.
Sample response:
{
"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:
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"}'
Sample response:
{
"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:
# 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:
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!
Last updated
Was this helpful?