Loop
Sign inGet started
API reference

Webhooks

Receive signed real-time Loop events, verify the Loop-Signature HMAC SHA-256 header, handle feedback.created and feedback.resolved, and use retries.

Overview

Webhooks let your systems react to feedback the moment it changes. When an event occurs, Loop sends an HTTP POST to the endpoint you configure, with a JSON body and a signature header. Delivery is exactly-once under normal conditions, with automatic retries and exponential backoff if your endpoint is unavailable.

Configure your endpoint URL and copy its signing secret from the Loop dashboard. The secret is used to verify every payload.

Event types

Each delivery carries an event type describing what happened:

Event Fires when
feedback.created A new feedback item is created
feedback.resolved An item's status changes to resolved

Payload shape

The request body is a JSON envelope. The data field contains the full feedback item, identical to the API response shape.

{
  "id": "evt_5b8e",
  "type": "feedback.created",
  "createdAt": "2026-06-19T14:02:11Z",
  "data": {
    "id": "fb_3a91",
    "user": { "id": "u_8f2c" },
    "message": "The CSV export timed out on large datasets.",
    "sentiment": "negative",
    "source": "in-app-widget",
    "tag": "exports",
    "status": "open",
    "createdAt": "2026-06-19T14:02:11Z"
  }
}

Every request also includes signature headers:

Header Description
Loop-Signature HMAC SHA-256 of the raw request body, hex-encoded
Loop-Timestamp Unix timestamp (seconds) of when the event was sent
Loop-Event-Id Stable event id, useful for idempotency

Verifying the signature

The Loop-Signature header is an HMAC SHA-256 of the raw, unparsed request body using your endpoint's signing secret. Always verify it before trusting a payload, and always compute the HMAC over the raw bytes — re-serializing parsed JSON will change the bytes and break verification.

const crypto = require('crypto');

function verifyLoopSignature(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody, 'utf8')
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || '');

  // Constant-time comparison to avoid timing attacks.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Wire it into an Express handler. Note the use of the raw body parser so the bytes are unchanged:

const express = require('express');
const app = express();

app.post(
  '/webhooks/loop',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.header('Loop-Signature');
    const secret = process.env.LOOP_WEBHOOK_SECRET;

    if (!verifyLoopSignature(req.body, signature, secret)) {
      return res.status(400).send('Invalid signature');
    }

    const event = JSON.parse(req.body.toString('utf8'));

    switch (event.type) {
      case 'feedback.created':
        // route the new item to triage
        break;
      case 'feedback.resolved':
        // notify the original reporter
        break;
    }

    // Acknowledge quickly with a 2xx.
    res.status(200).send('ok');
  }
);

Responding and retries

Return a 2xx status code as soon as you have accepted the event — do the heavy work asynchronously. Any non-2xx response, a timeout, or a connection failure marks the delivery as failed and schedules a retry.

Retries use exponential backoff, spreading attempts over an increasing interval:

Attempt Approximate delay after the previous try
1 immediate
2 ~1 minute
3 ~5 minutes
4 ~30 minutes
5 ~2 hours
6 ~6 hours

After the final attempt, the delivery is marked failed and surfaced in the dashboard, where you can replay it manually.

Idempotency and exactly-once delivery

Loop targets exactly-once delivery, but retries after an ambiguous failure can occasionally deliver the same event twice. Make your handler idempotent by de-duplicating on Loop-Event-Id:

if (await alreadyProcessed(req.header('Loop-Event-Id'))) {
  return res.status(200).send('duplicate ignored');
}

Next steps

You can now react to feedback in real time. Pair webhooks with the Feedback API to enrich, tag, and resolve items, or revisit Authentication to manage the keys behind your integration.

← PREVIOUS
Feedback API
NEXT →
SDKs