Webhooks

The Webhook integration lets TeamRetro send real-time HTTP notifications to any URL you control whenever events happen in a team — actions are created or completed, retrospectives finish, agreements change, and more. Unlike a Slack or Microsoft Teams integration that posts a human-readable message, webhooks deliver structured JSON, so you can wire TeamRetro into your own tooling, internal services, or automation platforms (Zapier, n8n, Make, custom Lambdas, etc.).

Requirement: Only TeamRetro Account Admins or Team Administrators can configure this integration. The webhook integration must also be enabled on your account's plan.


Where to find it

Webhooks are configured per team, so each team can send events to its own endpoint.

  1. Go to Team → Settings → Integrations.
  2. Find the Webhook card and click Connect.

The Webhook setup wizard opens with three steps: EventsEndpoint, and Test.


Step 1 — Choose your events

The first step lets you select which events should be sent to your webhook URL. By default a sensible set of common events is pre-selected; you can toggle individual events on or off.

Available event types include:

Actions

  • Action created
  • Action updated
  • Action completed
  • Action deleted
  • Action reassigned
  • Action due date changed

Meetings

  • Retrospective completed
  • Health check completed
  • Estimation meeting completed

Agreements

  • Agreement created
  • Agreement updated
  • Agreement deleted

Team membership

  • Team member invited
  • Team member removed

Mentions and kudos

  • Mention created (when someone is @mentioned)
  • Kudos given to a team member

For full event payload schemas, see the TeamRetro Developer Documentation.

Step 2 — Configure the endpoint

Enter the details of the URL TeamRetro should send events to.

  • Webhook URL (required) — The HTTPS endpoint that will receive event deliveries (e.g. https://your-server.com/webhook   ). Only https://    URLs on standard ports (443   , or 80    for HTTP in development) are accepted. Private IPs, localhost, and other non-routable addresses are blocked for security.
  • Custom Headers (optional) — Any additional HTTP headers you want sent with every delivery. Useful for adding an API key or routing token to identify TeamRetro to your downstream service. A small set of reserved headers (e.g. Authorization   , Cookie   , Host   ) is not allowed and will be rejected.
  • Signing Secret — A 64-character hex secret is auto-generated for you. Copy it and store it on the receiving side; you'll use it to verify the authenticity of every delivery (see Verifying webhook signatures). The secret is shown in a copy-to-clipboard field — keep it private.

If you change the URL or the secret later, simply re-open the integration and update the values.

Step 3 — Send a test event

Click Test Webhook to fire a test delivery immediately. TeamRetro sends a small webhook.test    payload to your URL and shows you the response inline:

  • The HTTP status code returned by your server
  • A truncated copy of the response body
  • Any connection error (timeout, DNS failure, TLS error, etc.)

The test delivery is signed exactly like a real event, so you can use it to validate your signature-verification code end-to-end before going live. Test deliveries are not stored in your delivery history.

Once a successful test has completed and you save the integration, the card on the Integrations page shows Connected and TeamRetro will start delivering live events.

Payload format

Every delivery is an HTTP POST    with Content-Type: application/json   . The body uses a consistent envelope:

{
  "event": "action.completed",
  "timestamp": "2024-03-15T10:30:00.000Z",
  "data": {
    "action": { "...": "..." }
  }
}
Field Description
event    The event type (e.g. action.completed   , retrospective.completed   ).
timestamp    ISO 8601 UTC timestamp of when the event occurred.
data    Event-specific payload. The keys inside data    depend on the event type.

All id    values are opaque strings — don't try to parse them.

Request headers

In addition to your custom headers, every delivery includes:

Header Description
Content-Type    Always application/json   
User-Agent    TeamRetro-Webhooks/1.0   
X-TeamRetro-Event-Type    The event type, also present in the body
X-TeamRetro-Webhook-Delivery-Id    A unique ID for this delivery — use it to deduplicate retries
X-TeamRetro-Signature    HMAC-SHA256 signature, see below

Verifying webhook signatures

Every delivery is signed with HMAC-SHA256 using the signing secret you copied during setup. You should verify this signature on every request you receive — it's how you know the event genuinely came from TeamRetro and hasn't been tampered with in transit.

The X-TeamRetro-Signature    header looks like:

t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

To verify:

  1. Extract t    (Unix timestamp in seconds) and v1    (the signature) from the header.
  2. Construct the signed payload as {t}.{raw-request-body}    — using the raw request body, byte-for-byte, before any JSON re-serialization.
  3. Compute HMAC-SHA256 of that string using your signing secret.
  4. Compare the computed signature to v1    using a timing-safe comparison.
  5. (Optional but recommended) Reject the request if t    is more than 5 minutes old to protect against replay attacks.

JavaScript example

const crypto = require("crypto");


function verifyWebhook(rawBody, signatureHeader, secret) {
  const [tPart, v1Part] = signatureHeader.split(",");
  const timestamp = tPart.split("=")[1];
  const signature = v1Part.split("=")[1];

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(signedPayload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

Python example

import hmac, hashlib


def verify_webhook(raw_body: str, signature_header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp = parts["t"]
    signature = parts["v1"]

    signed_payload = f"{timestamp}.{raw_body}"
    expected = hmac.new(
        secret.encode(), signed_payload.encode(), hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

Full code examples in additional languages are available in the TeamRetro Developer Documentation.

Delivery, retries, and failure handling

TeamRetro delivers each webhook event with a 30-second timeout. Your endpoint should respond with any 2xx    status code as quickly as possible, ideally within a few seconds — push slow work onto a queue rather than handling it inside the request.

Retries

If your endpoint returns a 5xx    error, times out, or can't be reached, TeamRetro retries the delivery automatically with exponential backoff:

Attempt

Delay before attempt

1

Immediate

2

After 1 minute

3

After 5 minutes

4

After 30 minutes

5

After 2 hours

That gives a total retry window of about 2.5 hours per event. After the fifth attempt the delivery is marked failed and dropped.

429 Too Many Requests    response causes the delivery to be rescheduled 60 seconds later and does not count against the retry budget. A 4xx    error other than 429    is treated as a permanent failure — TeamRetro will not retry, because the request was rejected by your server, not by infrastructure.

Auto-disable

If a webhook endpoint fails persistently, TeamRetro disables it automatically:

  • After 10 consecutive failed deliveries, the integration is moved into a Configuration Required state and stops sending new events.

To re-enable it, return to Team → Settings → Integrations, fix the underlying problem (URL change, expired auth, etc.), test the endpoint again, and save.

Idempotency

Because retries are possible, the same event may be delivered more than once. Use the X-TeamRetro-Webhook-Delivery-Id    header to deduplicate — TeamRetro guarantees this ID is unique per delivery attempt and stable across retries of the same event.

Security

  • HTTPS only — only HTTPS URLs on port 443 are accepted in production.
  • No private IPs — URLs that resolve to localhost, link-local, RFC1918 private ranges, or other non-routable addresses are rejected to prevent server-side request forgery (SSRF). DNS is re-checked on every delivery, and the resolved IP is pinned for the lifetime of the request to prevent DNS rebinding.
  • HMAC-SHA256 signatures — every delivery is signed; verify the signature before trusting the payload.
  • Custom headers — sensitive headers like Authorization   , Cookie    and Host    cannot be set as custom headers, to avoid accidental credential exfiltration.
  • Signing secret rotation — to rotate your secret, re-open the webhook integration, copy a new secret, and update your receiver. There is no zero-downtime rotation; coordinate the change with a brief acceptance window on the receiving side if needed.

Permissions and plan availability

  • Who can configure: Account Administrators and Team Administrators.
  • Plan requirement: The Webhook integration must be available on your TeamRetro plan. If it is not enabled, the Connect button on the Webhook card will be unavailable.

Troubleshooting

The Test Webhook step shows a "Private IPs and localhost are not allowed" error. Your URL resolves to a non-routable address. If you're trying to receive webhooks on a developer machine, use a tunnelling tool like ngrok    or cloudflared    to expose a public HTTPS URL.

My endpoint returns 200 but I never see the event. Check the X-TeamRetro-Event-Type    header rather than parsing the body — that's the fastest way to confirm what was delivered. Also verify your endpoint is consuming the raw request body before any framework middleware re-serializes it; signature verification depends on the exact bytes that were posted.

My integration was disabled automatically. That happens after 10 consecutive failed deliveries. Re-open the integration, fix the problem, run Test Webhook to confirm it works, and save. The integration moves back to Connected.

I rotated the secret on my server but deliveries are still failing. You also need to rotate the secret in TeamRetro: re-open the Webhook integration, copy the displayed Signing Secret, and update your receiver to match.

Where can I find the full payload schema for each event? See the TeamRetro Developer Documentation for the complete OpenAPI schema for every event type.

Still need help? Contact Us Contact Us