Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org
+162
-162
docs/DOCS.md
+162
-162
docs/DOCS.md
···
278
278
git push tangled main
279
279
```
280
280
281
-
# Webhooks
282
-
283
-
Webhooks allow you to receive HTTP POST notifications when events occur in your repositories. This enables you to integrate Tangled with external services, trigger CI/CD pipelines, send notifications, or automate workflows.
284
-
285
-
## Overview
286
-
287
-
Webhooks send HTTP POST requests to URLs you configure whenever specific events happen. Currently, Tangled supports push events, with more event types coming soon.
288
-
289
-
## Configuring webhooks
290
-
291
-
To set up a webhook for your repository:
292
-
293
-
1. Navigate to your repository settings
294
-
2. Click the "hooks" tab
295
-
3. Click "add webhook"
296
-
4. Configure your webhook:
297
-
- **Payload URL**: The endpoint that will receive the webhook POST requests
298
-
- **Secret**: An optional secret key for verifying webhook authenticity (auto-generated if left blank)
299
-
- **Events**: Select which events trigger the webhook (currently only push events)
300
-
- **Active**: Toggle whether the webhook is enabled
301
-
302
-
## Webhook payload
303
-
304
-
### Push
305
-
306
-
When a push event occurs, Tangled sends a POST request with a JSON payload of the format:
307
-
308
-
```json
309
-
{
310
-
"after": "7b320e5cbee2734071e4310c1d9ae401d8f6cab5",
311
-
"before": "c04ddf64eddc90e4e2a9846ba3b43e67a0e2865e",
312
-
"pusher": {
313
-
"did": "did:plc:hwevmowznbiukdf6uk5dwrrq"
314
-
},
315
-
"ref": "refs/heads/main",
316
-
"repository": {
317
-
"clone_url": "https://tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
318
-
"created_at": "2025-09-15T08:57:23Z",
319
-
"description": "an example repository",
320
-
"fork": false,
321
-
"full_name": "did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
322
-
"html_url": "https://tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
323
-
"name": "some-repo",
324
-
"open_issues_count": 5,
325
-
"owner": {
326
-
"did": "did:plc:hwevmowznbiukdf6uk5dwrrq"
327
-
},
328
-
"ssh_url": "ssh://git@tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
329
-
"stars_count": 1,
330
-
"updated_at": "2025-09-15T08:57:23Z"
331
-
}
332
-
}
333
-
```
334
-
335
-
## HTTP headers
336
-
337
-
Each webhook request includes the following headers:
338
-
339
-
- `Content-Type: application/json`
340
-
- `User-Agent: Tangled-Hook/<short-sha>` — User agent with short SHA of the commit
341
-
- `X-Tangled-Event: push` — The event type
342
-
- `X-Tangled-Hook-ID: <webhook-id>` — The webhook ID
343
-
- `X-Tangled-Delivery: <uuid>` — Unique delivery ID
344
-
- `X-Tangled-Signature-256: sha256=<hmac>` — HMAC-SHA256 signature (if secret configured)
345
-
346
-
## Verifying webhook signatures
347
-
348
-
If you configured a secret, you should verify the webhook signature to ensure requests are authentic. For example, in Go:
349
-
350
-
```go
351
-
package main
352
-
353
-
import (
354
-
"crypto/hmac"
355
-
"crypto/sha256"
356
-
"encoding/hex"
357
-
"io"
358
-
"net/http"
359
-
"strings"
360
-
)
361
-
362
-
func verifySignature(payload []byte, signatureHeader, secret string) bool {
363
-
// Remove 'sha256=' prefix from signature header
364
-
signature := strings.TrimPrefix(signatureHeader, "sha256=")
365
-
366
-
// Compute expected signature
367
-
mac := hmac.New(sha256.New, []byte(secret))
368
-
mac.Write(payload)
369
-
expected := hex.EncodeToString(mac.Sum(nil))
370
-
371
-
// Use constant-time comparison to prevent timing attacks
372
-
return hmac.Equal([]byte(signature), []byte(expected))
373
-
}
374
-
375
-
func webhookHandler(w http.ResponseWriter, r *http.Request) {
376
-
// Read the request body
377
-
payload, err := io.ReadAll(r.Body)
378
-
if err != nil {
379
-
http.Error(w, "Bad request", http.StatusBadRequest)
380
-
return
381
-
}
382
-
383
-
// Get signature from header
384
-
signatureHeader := r.Header.Get("X-Tangled-Signature-256")
385
-
386
-
// Verify signature
387
-
if signatureHeader != "" && verifySignature(payload, signatureHeader, yourSecret) {
388
-
// Webhook is authentic, process it
389
-
processWebhook(payload)
390
-
w.WriteHeader(http.StatusOK)
391
-
} else {
392
-
http.Error(w, "Invalid signature", http.StatusUnauthorized)
393
-
}
394
-
}
395
-
```
396
-
397
-
## Delivery retries
398
-
399
-
Webhooks are automatically retried on failure:
400
-
401
-
- **3 total attempts** (1 initial + 2 retries)
402
-
- **Exponential backoff** starting at 1 second, max 10 seconds
403
-
- **Retried on**:
404
-
- Network errors
405
-
- HTTP 5xx server errors
406
-
- **Not retried on**:
407
-
- HTTP 4xx client errors (bad request, unauthorized, etc.)
408
-
409
-
### Timeouts
410
-
411
-
Webhook requests timeout after 30 seconds. If your endpoint needs more time:
412
-
413
-
1. Respond with 200 OK immediately
414
-
2. Process the webhook asynchronously in the background
415
-
416
-
## Example integrations
417
-
418
-
### Discord notifications
419
-
420
-
```javascript
421
-
app.post("/webhook", (req, res) => {
422
-
const payload = req.body;
423
-
424
-
fetch("https://discord.com/api/webhooks/...", {
425
-
method: "POST",
426
-
headers: { "Content-Type": "application/json" },
427
-
body: JSON.stringify({
428
-
content: `New push to ${payload.repository.full_name}`,
429
-
embeds: [
430
-
{
431
-
title: `${payload.pusher.login} pushed to ${payload.ref}`,
432
-
url: payload.repository.html_url,
433
-
color: 0x00ff00,
434
-
},
435
-
],
436
-
}),
437
-
});
438
-
439
-
res.status(200).send("OK");
440
-
});
441
-
```
442
-
443
281
# Knot self-hosting guide
444
282
445
283
So you want to run your own knot server? Great! Here are a few prerequisites:
···
1383
1221
secret_id="$(cat /tmp/openbao/secret-id)"
1384
1222
```
1385
1223
1224
+
# Webhooks
1225
+
1226
+
Webhooks allow you to receive HTTP POST notifications when events occur in your repositories. This enables you to integrate Tangled with external services, trigger CI/CD pipelines, send notifications, or automate workflows.
1227
+
1228
+
## Overview
1229
+
1230
+
Webhooks send HTTP POST requests to URLs you configure whenever specific events happen. Currently, Tangled supports push events, with more event types coming soon.
1231
+
1232
+
## Configuring webhooks
1233
+
1234
+
To set up a webhook for your repository:
1235
+
1236
+
1. Navigate to your repository settings
1237
+
2. Click the "hooks" tab
1238
+
3. Click "add webhook"
1239
+
4. Configure your webhook:
1240
+
- **Payload URL**: The endpoint that will receive the webhook POST requests
1241
+
- **Secret**: An optional secret key for verifying webhook authenticity (auto-generated if left blank)
1242
+
- **Events**: Select which events trigger the webhook (currently only push events)
1243
+
- **Active**: Toggle whether the webhook is enabled
1244
+
1245
+
## Webhook payload
1246
+
1247
+
### Push
1248
+
1249
+
When a push event occurs, Tangled sends a POST request with a JSON payload of the format:
1250
+
1251
+
```json
1252
+
{
1253
+
"after": "7b320e5cbee2734071e4310c1d9ae401d8f6cab5",
1254
+
"before": "c04ddf64eddc90e4e2a9846ba3b43e67a0e2865e",
1255
+
"pusher": {
1256
+
"did": "did:plc:hwevmowznbiukdf6uk5dwrrq"
1257
+
},
1258
+
"ref": "refs/heads/main",
1259
+
"repository": {
1260
+
"clone_url": "https://tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
1261
+
"created_at": "2025-09-15T08:57:23Z",
1262
+
"description": "an example repository",
1263
+
"fork": false,
1264
+
"full_name": "did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
1265
+
"html_url": "https://tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
1266
+
"name": "some-repo",
1267
+
"open_issues_count": 5,
1268
+
"owner": {
1269
+
"did": "did:plc:hwevmowznbiukdf6uk5dwrrq"
1270
+
},
1271
+
"ssh_url": "ssh://git@tangled.org/did:plc:hwevmowznbiukdf6uk5dwrrq/some-repo",
1272
+
"stars_count": 1,
1273
+
"updated_at": "2025-09-15T08:57:23Z"
1274
+
}
1275
+
}
1276
+
```
1277
+
1278
+
## HTTP headers
1279
+
1280
+
Each webhook request includes the following headers:
1281
+
1282
+
- `Content-Type: application/json`
1283
+
- `User-Agent: Tangled-Hook/<short-sha>` — User agent with short SHA of the commit
1284
+
- `X-Tangled-Event: push` — The event type
1285
+
- `X-Tangled-Hook-ID: <webhook-id>` — The webhook ID
1286
+
- `X-Tangled-Delivery: <uuid>` — Unique delivery ID
1287
+
- `X-Tangled-Signature-256: sha256=<hmac>` — HMAC-SHA256 signature (if secret configured)
1288
+
1289
+
## Verifying webhook signatures
1290
+
1291
+
If you configured a secret, you should verify the webhook signature to ensure requests are authentic. For example, in Go:
1292
+
1293
+
```go
1294
+
package main
1295
+
1296
+
import (
1297
+
"crypto/hmac"
1298
+
"crypto/sha256"
1299
+
"encoding/hex"
1300
+
"io"
1301
+
"net/http"
1302
+
"strings"
1303
+
)
1304
+
1305
+
func verifySignature(payload []byte, signatureHeader, secret string) bool {
1306
+
// Remove 'sha256=' prefix from signature header
1307
+
signature := strings.TrimPrefix(signatureHeader, "sha256=")
1308
+
1309
+
// Compute expected signature
1310
+
mac := hmac.New(sha256.New, []byte(secret))
1311
+
mac.Write(payload)
1312
+
expected := hex.EncodeToString(mac.Sum(nil))
1313
+
1314
+
// Use constant-time comparison to prevent timing attacks
1315
+
return hmac.Equal([]byte(signature), []byte(expected))
1316
+
}
1317
+
1318
+
func webhookHandler(w http.ResponseWriter, r *http.Request) {
1319
+
// Read the request body
1320
+
payload, err := io.ReadAll(r.Body)
1321
+
if err != nil {
1322
+
http.Error(w, "Bad request", http.StatusBadRequest)
1323
+
return
1324
+
}
1325
+
1326
+
// Get signature from header
1327
+
signatureHeader := r.Header.Get("X-Tangled-Signature-256")
1328
+
1329
+
// Verify signature
1330
+
if signatureHeader != "" && verifySignature(payload, signatureHeader, yourSecret) {
1331
+
// Webhook is authentic, process it
1332
+
processWebhook(payload)
1333
+
w.WriteHeader(http.StatusOK)
1334
+
} else {
1335
+
http.Error(w, "Invalid signature", http.StatusUnauthorized)
1336
+
}
1337
+
}
1338
+
```
1339
+
1340
+
## Delivery retries
1341
+
1342
+
Webhooks are automatically retried on failure:
1343
+
1344
+
- **3 total attempts** (1 initial + 2 retries)
1345
+
- **Exponential backoff** starting at 1 second, max 10 seconds
1346
+
- **Retried on**:
1347
+
- Network errors
1348
+
- HTTP 5xx server errors
1349
+
- **Not retried on**:
1350
+
- HTTP 4xx client errors (bad request, unauthorized, etc.)
1351
+
1352
+
### Timeouts
1353
+
1354
+
Webhook requests timeout after 30 seconds. If your endpoint needs more time:
1355
+
1356
+
1. Respond with 200 OK immediately
1357
+
2. Process the webhook asynchronously in the background
1358
+
1359
+
## Example integrations
1360
+
1361
+
### Discord notifications
1362
+
1363
+
```javascript
1364
+
app.post("/webhook", (req, res) => {
1365
+
const payload = req.body;
1366
+
1367
+
fetch("https://discord.com/api/webhooks/...", {
1368
+
method: "POST",
1369
+
headers: { "Content-Type": "application/json" },
1370
+
body: JSON.stringify({
1371
+
content: `New push to ${payload.repository.full_name}`,
1372
+
embeds: [
1373
+
{
1374
+
title: `${payload.pusher.did} pushed to ${payload.ref}`,
1375
+
url: payload.repository.html_url,
1376
+
color: 0x00ff00,
1377
+
},
1378
+
],
1379
+
}),
1380
+
});
1381
+
1382
+
res.status(200).send("OK");
1383
+
});
1384
+
```
1385
+
1386
1386
# Migrating knots and spindles
1387
1387
1388
1388
Sometimes, non-backwards compatible changes are made to the
History
5 rounds
1 comment
anirudh.fi
submitted
#4
1 commit
expand
collapse
docs: document webhooks
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
pull request successfully merged
anirudh.fi
submitted
#3
1 commit
expand
collapse
docs: document webhooks
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#2
1 commit
expand
collapse
docs: document webhooks
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#1
1 commit
expand
collapse
docs: document webhooks
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
3/3 success
expand
collapse
expand 0 comments
anirudh.fi
submitted
#0
1 commit
expand
collapse
docs: document webhooks
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
pusher.login, onlypusher.didchangeset lgtm otherwise!
will give this feature a test locally to identify bugs if any!