pingd docs

Webhooks

A webhook is an inbound URL that turns arbitrary JSON into a published message on one topic. Use them to wire third-party services into your topics without writing a custom integration.

How it works

  1. You create a webhook attached to a topic, with a template that maps fields from incoming JSON into a message payload.
  2. pingd generates a one-time bearer-style token and returns the receive URL: POST /hooks/:token.
  3. The third-party service POSTs JSON to that URL.
  4. pingd renders the template against the JSON, publishes the message.
  5. Normal dispatch fanout happens (push, SSE, history).

Create a webhook

curl -s http://localhost:7685/topics/ci.github/webhooks \
  -H 'Authorization: Bearer $TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "template": {
      "title": "{{repository.full_name}} push",
      "body": "{{head_commit.author.name}}: {{head_commit.message}}",
      "tags": "github,{{repository.name}}",
      "priority": 2
    }
  }'
pingd-cli webhooks create \
  --topic ci.github \
  --template template.json
import requests

requests.post(
    "http://localhost:7685/topics/ci.github/webhooks",
    headers={"Authorization": "Bearer TOKEN"},
    json={
        "template": {
            "title": "{{repository.full_name}} push",
            "body": "{{head_commit.author.name}}: {{head_commit.message}}",
            "tags": "github,{{repository.name}}",
            "priority": 2,
        },
    },
)

Response (token shown once):

{
  "id": "uuid",
  "topicID": "uuid",
  "token": "whk_...",
  "template": { "...": "..." },
  "createdAt": "..."
}

The receive URL is:

POST /hooks/whk_...

Template fields

FieldTypeNotes
titletemplate stringOptional message title
subtitletemplate stringOptional subtitle
bodytemplate stringFalls back to the raw request body if empty
tagscomma-separated templateSplit on commas, trimmed
priority1–3Optional, default 2
ttlsecondsOptional, max 30 days

Template syntax

Templates use {{path.to.field}} lookups against the incoming JSON. Examples:

{{alert.title}}
{{source}}
{{event.repository.full_name}}

Missing fields render as empty string. There are no conditionals or loops, by design.

Use a number in the path to access an array element by position (zero-indexed):

{{episodes.0.title}}
{{users.1.name}}
{{tags.0}}

Out-of-range indexes render as empty.

You can also render a whole array. {{tags}} joins its elements with , :

input:  { "tags": ["release", "urgent"] }
output: "release, urgent"

Nested arrays, objects, and null inside the array are skipped.

Receive payload

Webhook receive does not use bearer auth. The token in the URL is the credential.

curl -s http://localhost:7685/hooks/whk_example \
  -H 'Content-Type: application/json' \
  -d '{
    "alert": { "title": "Disk full" },
    "source": "nas",
    "message": "Volume /data is 95% full",
    "service": "storage"
  }'

Successful receives return 202 Accepted.

Endpoints

MethodPathAuth
GET/topics/:name/webhooksOwner / admin
POST/topics/:name/webhooksOwner / admin
GET/webhooks/:idOwner / admin
PATCH/webhooks/:idOwner / admin
DELETE/webhooks/:idOwner / admin
POST/hooks/:tokenToken in URL only
Webhook management is owner/admin-only. A user with rw permission on a topic cannot create webhooks for it.

Worked example: GitHub push events

Point GitHub's webhook at https://your-pingd/hooks/whk_.... For a payload like:

{
  "repository": {
    "full_name": "acme/inventory",
    "name": "inventory"
  },
  "head_commit": {
    "author": { "name": "alice" },
    "message": "fix tax rounding"
  }
}

The template renders a message on ci.github:

title:  acme/inventory push
body:   alice: fix tax rounding
tags:   github, inventory

Save your template to a file (template.json):

{
  "title": "{{repository.full_name}} push",
  "body": "{{head_commit.author.name}}: {{head_commit.message}}",
  "tags": "github,{{repository.name}}",
  "priority": 2
}

Rate limiting

Webhook receives are rate-limited per token and per IP, separately from the general API. Tune them with PINGD_WEBHOOK_RATE_LIMIT_PER_TOKEN and PINGD_WEBHOOK_RATE_LIMIT_PER_IP.

Security notes