GetABrain.ai

Webhooks

Webhooks let you receive real-time HTTP notifications when events happen on your queries. Instead of polling the API, configure a webhook URL and GetABrain.ai will push event data to your server as it occurs.

Setting Up Webhooks

There are two ways to configure webhooks:

  1. Per-query: Include a webhook_url field when creating a query. Events for that query will be sent to the specified URL.
  2. Account-level: Set a default webhook URL via PUT /requestor/webhook with { "webhook_url": "https://..." }. The response includes your signing webhook secret (shown once). All query events go there unless overridden per-query. URLs must be public https://.

Example: Creating a Query with Webhook

curl -X POST https://www.getabrain.ai/api/v1/queries \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -H "X-API-Secret: your_api_secret" \
  -d '{
    "type": "text",
    "title": "Product Feedback",
    "content_data": {
      "question": "What do you think of our checkout flow?"
    },
    "required_responses": 3,
    "bid_amount_cents": 50,
    "webhook_url": "https://your-server.com/webhooks/getabrain"
  }'

Webhook Events

GetABrain.ai sends the following event types:

EventTrigger
response.submittedA worker submits a response to your query
query.completedAll required responses have been collected and the query is marked complete

response.submitted Payload

{
  "event": "response.submitted",
  "timestamp": "2025-01-15T10:35:00Z",
  "data": {
    "query_id": "550e8400-e29b-41d4-a716-446655440000",
    "response_id": "resp-001",
    "response_data": {
      "answer": "The checkout flow is smooth but could use a progress indicator."
    },
    "quality_score": 0.91,
    "received_responses": 1,
    "required_responses": 3
  }
}

query.completed Payload

{
  "event": "query.completed",
  "timestamp": "2025-01-15T10:45:00Z",
  "data": {
    "query_id": "550e8400-e29b-41d4-a716-446655440000",
    "title": "Product Feedback",
    "type": "text",
    "required_responses": 3,
    "received_responses": 3,
    "total_cost_cents": 150,
    "responses": [
      {
        "id": "resp-001",
        "response_data": { "answer": "The checkout flow is smooth." },
        "quality_score": 0.91
      },
      {
        "id": "resp-002",
        "response_data": { "answer": "Add a progress bar to the checkout." },
        "quality_score": 0.88
      },
      {
        "id": "resp-003",
        "response_data": { "answer": "Consider adding guest checkout." },
        "quality_score": 0.93
      }
    ]
  }
}

Signature Verification

Every webhook request includes an HMAC SHA-256 signature in the X-GAB-Signature header. The signature is computed using your webhook secret (returned once by PUT /requestor/webhook) as the key and the raw request body as the message — not your API secret, which the server never stores in a signable form. Always verify this signature before processing webhooks. Deliveries time out after 10 seconds; a non-2xx answer (including redirects) is retried roughly every minute, up to 5 attempts.

Headers Sent

HeaderDescription
Content-Typeapplication/json
X-GAB-SignatureHMAC SHA-256 hex digest of the request body
X-GAB-EventThe event type (e.g., response.submitted)
X-GAB-TimestampISO 8601 timestamp of when the event was sent

Node.js Verification Example

import crypto from "crypto";

function verifyWebhookSignature(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body, "utf-8")
    .digest("hex");

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

// In your webhook handler:
app.post("/webhooks/getabrain", (req, res) => {
  const signature = req.headers["x-gab-signature"];
  const rawBody = req.rawBody; // Ensure you capture the raw body

  if (!verifyWebhookSignature(rawBody, signature, process.env.GAB_WEBHOOK_SECRET)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = req.body;
  console.log("Received event:", event.event);

  // Process the event...

  res.status(200).json({ received: true });
});

Python Verification Example

import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode("utf-8"),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route("/webhooks/getabrain", methods=["POST"])
def webhook_handler():
    signature = request.headers.get("X-GAB-Signature")
    if not verify_signature(request.data, signature, "your_api_secret"):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.json
    print(f"Received event: {event['event']}")

    # Process the event...

    return jsonify({"received": True}), 200

Retry Policy

If your webhook endpoint returns a non-2xx status code (or a redirect) or does not respond within 10 seconds, GetABrain.ai will redeliver the event roughly once a minute, for up to 5 attempts total.

After 5 failed attempts, the delivery is dead-lettered and no further retries are made. You can check for missed events by polling the query endpoint.

Best Practices

  • Always verify signatures -- never process a webhook without validating the X-GAB-Signature header.
  • Respond quickly -- return a 200 status code within 10 seconds. Perform heavy processing asynchronously.
  • Handle duplicates -- webhook deliveries may be retried, so use the event timestamp and query/response IDs to deduplicate.
  • Use HTTPS -- webhook URLs must use HTTPS. HTTP endpoints will be rejected.
  • Log everything -- store raw webhook payloads for debugging and auditing purposes.