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:
- Per-query: Include a
webhook_urlfield when creating a query. Events for that query will be sent to the specified URL. - Account-level: Set a default webhook URL in your requestor dashboard settings. All query events will be sent there unless overridden per-query.
Example: Creating a Query with Webhook
curl -X POST https://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:
| Event | Trigger |
|---|---|
response.submitted | A worker submits a response to your query |
query.completed | All 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 API secret as the key and the raw request body as the message. Always verify this signature before processing webhooks.
Headers Sent
| Header | Description |
|---|---|
Content-Type | application/json |
X-GAB-Signature | HMAC SHA-256 hex digest of the request body |
X-GAB-Event | The event type (e.g., response.submitted) |
X-GAB-Timestamp | ISO 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_API_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}), 200Retry Policy
If your webhook endpoint returns a non-2xx status code or does not respond within 10 seconds, GetABrain.ai will retry the delivery with exponential backoff:
| Attempt | Delay | Description |
|---|---|---|
| 1st retry | 1 minute | First retry after initial failure |
| 2nd retry | 5 minutes | Second retry with increased delay |
| 3rd retry | 30 minutes | Final retry attempt |
After 3 failed retries, the webhook delivery is marked as failed. You can check for missed events by polling the query endpoint or reviewing your webhook delivery logs in the dashboard.
Best Practices
- Always verify signatures -- never process a webhook without validating the
X-GAB-Signatureheader. - 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.