Documentation
Webhooks

Webhooks

Receive real-time HTTP notifications when things happen to pull requests in your team's repositories.

Why Tenpace webhooks are different: GitHub already sends webhooks for basic events like PR opened or review submitted. Tenpace webhooks add workflow-level events that GitHub can't compute: pr.ready_to_merge, review.round_complete, review.sla_breached, and pr.attention_needed.

Setting up a webhook

Via the dashboard

  1. Navigate to Settings → Webhooks in your Tenpace dashboard.
  2. Click Add Endpoint.
  3. Enter your HTTPS endpoint URL.
  4. Select the events you want to subscribe to (or choose All events).
  5. Click Create — you'll see the webhook secret once. Copy it now.

Via the API

POST /api/webhooks
Authorization: Bearer <api-key>
Content-Type: application/json

{
  "url": "https://example.com/hooks/tenpace",
  "events": ["pr.ready_to_merge", "review.sla_breached"],
  "description": "Merge automation handler"
}

The response includes a secret field — this is the only time the raw secret is returned. Store it securely.

Verifying signatures

Every webhook request includes an X-Tenpace-Signature header. Always verify it before processing the payload to prevent spoofed requests.

The signature is computed as sha256=HMAC-SHA256(secret, body).

Node.js

const crypto = require('crypto');

function verifySignature(secret, rawBody, signatureHeader) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

// Express example
app.post('/hooks/tenpace', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-tenpace-signature'];
  if (!verifySignature(process.env.WEBHOOK_SECRET, req.body, sig)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  console.log('Received event:', event.event);
  res.sendStatus(200);
});

Python

import hmac, hashlib

def verify_signature(secret: str, body: bytes, header: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)

# Flask example
@app.route('/hooks/tenpace', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Tenpace-Signature', '')
    if not verify_signature(os.environ['WEBHOOK_SECRET'], request.get_data(), sig):
        return 'Invalid signature', 401
    event = request.get_json(force=True)
    print(f"Received event: {event['event']}")
    return '', 200

Go

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func verifySignature(secret string, body []byte, header string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(header))
}

Request headers

HeaderDescription
X-Tenpace-SignatureHMAC-SHA256 of the request body, prefixed with sha256=
X-Tenpace-EventEvent type string, e.g. pr.opened
X-Tenpace-DeliveryUnique delivery ID (UUID-like) for idempotency
X-Tenpace-TimestampUnix timestamp (seconds) of the delivery
Content-Typeapplication/json

Payload envelope

All events share the same top-level structure:

{
  "event": "pr.opened",          // Event type
  "timestamp": "2025-01-01T12:00:00.000Z",
  "data": { ... }                // Event-specific payload
}

Retry policy

If your endpoint returns a non-2xx status or times out, Tenpace retries delivery with exponential backoff:

AttemptDelay
1st retry10 seconds
2nd retry60 seconds
3rd retry5 minutes

After 3 retries the delivery is marked failed. If an endpoint accumulates 10 consecutive failures across any deliveries, it is automatically disabled to prevent abuse. You can re-enable it from the dashboard or API.

Each delivery attempt times out after 10 seconds. Make sure your endpoint responds quickly — if you need to do heavy processing, respond 200 immediately and process asynchronously.

Event types

See the full Event Reference → for complete payloads and examples.

GitHub Pass-through

  • pr.opened
  • pr.merged
  • pr.closed
  • pr.review_submitted
  • pr.review_requested
  • pr.ci_status

Tenpace Workflow Events

  • pr.ready_to_merge
  • pr.attention_needed
  • review.round_complete
  • review.sla_breached