Webhooks — Grep API v2

Webhooks

Skip polling — register a callback URL and Grep will POST to it when the job is done. HMAC-signed, retried up to three times.

Register a webhook

Pass webhook_url when creating a job. Use HTTPS for production webhooks; HTTP is only appropriate for local development. Production deployments also reject private network targets.

curl
curl -X POST "https://api.grep.ai/api/v2/research" \
  -H "Authorization: Bearer $GREP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Due diligence on Stripe",
    "effort": "medium",
    "webhook_url": "https://your-app.com/grep/webhooks/completion"
  }'
  • webhook_url POSTed once when the job reaches a terminal state (completed or failed).

HMAC signatures

Every webhook delivery includes an X-Signature-SHA256 header. The signature is base64(HMAC-SHA256(api_key, raw JSON body)). Verify before trusting the payload — this protects against spoofed callbacks.

http
POST /grep/webhooks/completion HTTP/1.1
Host: your-app.com
Content-Type: application/json
X-Signature-SHA256: QzJrZ1hnbmJZVHZjTzRweExuZmlEZ0twYkpqVkJkR0kzT2pQbm1PTz0=
X-Request-ID: 7c9e6679-7425-40de-944b-e07fc1f90ae7
X-Grep-Original-Request-ID: 5d4b8e10-1a4c-4f3a-8bd7-3a2e1c4d5b8f
X-Grep-Job-ID: 550e8400-e29b-41d4-a716-446655440000

FastAPI verification example. Note the constant-time compare:

python
import base64
import hmac
import hashlib
import os
from fastapi import FastAPI, Header, HTTPException, Request

API_KEY = os.environ["GREP_API_KEY"]
app = FastAPI()

@app.post("/grep/webhooks/completion")
async def handle_webhook(
    request: Request,
    x_signature_sha256: str = Header(..., alias="X-Signature-SHA256"),
):
    body = await request.body()
    # Signature is base64(HMAC-SHA256(api_key, raw_json_body)).
    expected = base64.b64encode(
        hmac.new(API_KEY.encode(), body, hashlib.sha256).digest()
    ).decode()
    if not hmac.compare_digest(expected, x_signature_sha256):
        raise HTTPException(401, "bad signature")
    # ... handle the verified payload ...
    return {"ok": True}

Correlation headers

Three v2 headers help you correlate webhooks with originating requests and downstream logs:

HeaderPurpose
X-Request-IDPer-delivery ID. Unique to this webhook attempt — useful when you need to dedupe across retries.
X-Grep-Original-Request-IDRequest ID of the originating POST /research call. Stable across all webhooks for the same job.
X-Grep-Job-IDThe job_id this webhook is about. Same as the body field — duplicated as a header for routing convenience.

Retry policy

If your endpoint returns a non-2xx status (or times out), Grep retries with exponential backoff.

  • Up to 3 attempts per webhook.
  • Total retry budget is 30 seconds — beyond that the delivery is dropped.
  • Any 2xx response is success. We don't inspect the body.

v1 vs v2 webhooks

If you're migrating from v1, here's what changed:

Aspectv1v2
Signature headerX-Parcha-SignatureX-Signature-SHA256
Correlation headersJob ID only, in bodyX-Request-ID, X-Grep-Original-Request-ID, X-Grep-Job-ID
Body shapeFlat — fields at top levelFlat — event, job_id, status, report, and optional structured_output