Build with the API

Webhooks

Signed events delivered to your own endpoint, with retries, and how to verify them.

A webhook is Gridline calling you. Instead of asking "is the report ready yet?" over and over, you give Gridline an address and it sends you a message the moment something happens. Webhooks are signed, so you can be sure a message really came from us.

In the dashboard

Admins can manage endpoints without code under Developers → Webhooks: add an endpoint (its signing secret is shown once), Send a test, pause or resume it, replace its secret, remove it, and read every delivery with its status, response code and attempts. A failed delivery has a Send again button.

Set one up

POST/outgoing-webhooksAdmins only, with a session. API keys cannot manage webhooks.
namestringrequired
A label for you, up to 100 characters.
urlstringrequired
Where to send events. Use HTTPS. Addresses that point to private networks are refused.
eventsstring[]required
Which events you want. See the list below.
curl https://gridline-data-analysis-app.duckdns.org/api/outgoing-webhooks \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slack bridge",
    "url": "https://example.com/hooks/gridline",
    "events": ["report.ready", "rules.failed"]
  }'

How many endpoints you can have depends on your plan: Free 1, Basic 5, Premium no limit.

Events

EventSent when`data` contains
file.uploadedA file or a new version was saved.fileId, datasetId, version
file.sensitive_data_foundA report found personal or secret data in a file the whole company can see.fileId, datasetId, version, columns (names and kinds, never values)
dataset.changedA new version was compared row by row with the one before it, and rows differ.fileId, datasetId, version, previousVersion, added, removed, changed, unchanged
dataset.schema_changedA new version removed or retyped a column its predecessor had.datasetId, fileId, version, previousVersion, columnsAdded, columnsRemoved, typeChanges
report.readyA report finished successfully.fileId, reportStatus
report.failedA report failed for good.fileId, reportStatus
rules.failedA file failed an error rule.fileId, failedRuleCount, qualityScore
quota.thresholdThe company reached 80% or 100% of its file quota.threshold, plan, periodKey, filesUsed, filesLimit
invoice.finalizedAn invoice was issued.invoiceId, totalCents, periodStart, periodEnd

There is also ping, which you trigger yourself to test an endpoint (POST /outgoing-webhooks/{id}/ping). Events carry ids and numbers, never cell values. When you need details, fetch them with an API key.

What you receive

A POST with a JSON body:

Body
{
  "id": "6f1e0c52-6a5e-4a52-8a7d-3b6b0b8d2f10",
  "type": "report.ready",
  "createdAt": "2026-10-02T09:14:07.014Z",
  "companyId": "…",
  "schemaVersion": 1,
  "data": { "fileId": "5b0c1f1e-…", "reportStatus": "ready" }
}

and these headers:

webhook-idoptional
The event's id. It is the same on every retry of the same event, so you can use it to ignore duplicates.
webhook-timestampoptional
When it was sent, in seconds since 1970.
webhook-signatureoptional
v1= followed by the signature (see below).
user-agentoptional
Gridline-Webhooks/1.0.

Check the signature

Anyone can send a request to your address, so verify every one. The signature is an HMAC-SHA256 of the timestamp, a dot, and the exact raw body, keyed with your whsec_… secret.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function isGenuine(secret, headers, rawBody) {
  const timestamp = headers["webhook-timestamp"];
  const received = headers["webhook-signature"];

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

  const a = Buffer.from(expected);
  const b = Buffer.from(received ?? "");
  if (a.length !== b.length || !timingSafeEqual(a, b)) return false;

  // Reject old messages, so a captured one cannot be replayed later.
  return Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
}

Replying

Answer with any 2xx status as soon as you have received the event, and do the real work afterwards. Gridline treats anything else (or no answer) as a failure.

Retries

  • A failed delivery is retried up to 5 times in all, with a growing delay between attempts.
  • Delivery is at least once: an event can arrive twice. Use webhook-id to skip ones you have already handled.
  • An answer of `410 Gone` means "stop sending these": the endpoint is disabled at once.
  • After 20 failed deliveries in a row, the endpoint is disabled automatically. Fix the receiver, then turn it back on with PATCH /outgoing-webhooks/{id} and { "active": true }.
  • Events are sent after the change is saved. If an upload is rolled back, no event leaves.

Manage endpoints

  • GET /outgoing-webhooks: list your endpoints.
  • PATCH /outgoing-webhooks/{id}: change the name, url, events or active.
  • DELETE /outgoing-webhooks/{id}: remove one.
  • POST /outgoing-webhooks/{id}/rotate-secret: make a new secret (shown once). The old one stops signing at once, so deliveries in flight use the new one. Update your receiver first, or accept a few failed checks.
  • GET /outgoing-webhooks/{id}/deliveries: what was sent, with status, attempts, the answer's status and the last error. Kept for 30 days.
  • POST /outgoing-webhooks/deliveries/{deliveryId}/redeliver: send one again.