Skip to main content

How webhooks work

When NearIQ detects a competitive change, it can POST a JSON payload to a URL you register. This lets you build real-time integrations — send an alert to Slack, trigger a workflow, or update your own dashboard.

Registering a webhook

curl -X POST https://app.neariq.io/api/v1/webhooks \
  -H "X-NearIQ-Key: niq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/neariq",
    "events": ["competitor.rating_change", "competitor.review_surge"]
  }'
Webhook URLs must use HTTPS, must not include embedded credentials, and must be publicly routable; localhost, private, link-local, multicast, and reserved network hosts are rejected. NearIQ also re-checks DNS resolution before delivery and deactivates endpoints that resolve to non-public addresses. NearIQ generates and returns the signing secret once when the webhook is created. Supported events are competitor.rating_change, competitor.review_surge, competitor.hours_change, competitor.price_change, competitor.status_change, competitor.website_change, competitor.new_competitor, business.rating_change, business.review_surge, alert.created, api_key.suspended, api_key.reactivated, and api_key.revoked_by_admin.

Payload format

{
  "id": "0bff1ff0-98ab-4c2e-a3ee-4c0b39c090a1",
  "event": "competitor.rating_change",
  "createdAt": "2026-04-27T09:14:22Z",
  "data": {
    "alert": {
      "id": "8e0a8c1d-2a95-4d17-a96b-1ef6b47681c0",
      "type": "rating_change",
      "severity": "opportunity",
      "title": "Rival Pizza dropped from 4.6 to 4.3 stars",
      "description": "Recent reviews suggest service speed is becoming a weakness.",
      "recommendation": "Reply to your latest positive reviews and highlight fast pickup in your next post.",
      "createdAt": "2026-04-27T09:14:22Z",
      "isOwnBusiness": false,
      "metadata": null
    },
    "competitor": {
      "id": "comp_xyz",
      "name": "Rival Pizza",
      "placeId": "ChIJ..."
    }
  }
}

Verifying signatures

NearIQ signs every webhook delivery using HMAC-SHA256. Verify the signature to ensure the request came from NearIQ:
import { createHmac } from 'crypto'

function verifyWebhook(payload: string, timestamp: string, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex')
  return signature === `sha256=${expected}`
}

// In your webhook handler:
const sig = req.headers['x-neariq-signature']
const timestamp = req.headers['x-neariq-timestamp']
const raw = req.rawBody // unparsed request body string
if (!verifyWebhook(raw, timestamp, sig, process.env.NEARIQ_WEBHOOK_SECRET)) {
  return res.status(401).send('Invalid signature')
}
Every delivery includes X-NearIQ-Delivery, X-NearIQ-Event, X-NearIQ-Timestamp, and X-NearIQ-Signature. The signature uses the exact raw request body, prefixed by the timestamp.

Retry behavior

NearIQ retries failed deliveries (non-2xx response or timeout) with exponential backoff:
  • Retry delays start at about 1 minute
  • Delays increase exponentially and cap at about 1 hour
  • Deliveries are attempted up to 8 times
After the final attempt, the delivery is marked dead. The endpoint remains registered so you can fix it without recreating the secret. You can review failed webhook deliveries from the dashboard alert details panel. The panel shows the event type, HTTP response status, retry count, next retry time, and a sanitized payload preview. After fixing your receiver, use the alert’s retry action to queue failed or dead deliveries again.

Webhook management endpoints

All webhook management endpoints require an API key with the webhooks:manage scope and a Growth+ plan.

GET /webhooks — List webhooks

curl https://app.neariq.io/api/v1/webhooks \
  -H "X-NearIQ-Key: niq_your_key_here"
{
  "webhooks": [
    {
      "id": "wh_abc123",
      "url": "https://yourapp.com/hooks/neariq",
      "events": ["competitor.rating_change", "competitor.review_surge"],
      "active": true,
      "createdAt": "2026-04-27T09:14:22Z",
      "lastTriggeredAt": "2026-05-01T12:00:00Z"
    }
  ]
}

POST /webhooks — Register a webhook

curl -X POST https://app.neariq.io/api/v1/webhooks \
  -H "X-NearIQ-Key: niq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/hooks/neariq",
    "events": ["competitor.rating_change", "alert.created"]
  }'
FieldTypeRequiredNotes
urlstringyesMust use HTTPS. No localhost, private, or reserved hosts.
eventsstring[]yesAt least one event type. See supported events above.
{
  "webhook": {
    "id": "wh_abc123",
    "url": "https://yourapp.com/hooks/neariq",
    "events": ["competitor.rating_change", "alert.created"],
    "active": true,
    "secret": "whsec_abc123...xyz",
    "createdAt": "2026-05-01T12:00:00Z"
  }
}
The secret is returned only on creation. Store it securely — it cannot be retrieved again.

PATCH /webhooks/:id — Update a webhook

curl -X PATCH https://app.neariq.io/api/v1/webhooks/wh_abc123 \
  -H "X-NearIQ-Key: niq_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["competitor.rating_change", "competitor.review_surge", "alert.created"],
    "active": true
  }'
FieldTypeRequiredNotes
urlstringnoNew HTTPS endpoint URL.
eventsstring[]noReplace the event list.
activebooleannoSet false to pause deliveries without deleting.
{
  "webhook": {
    "id": "wh_abc123",
    "url": "https://yourapp.com/hooks/neariq",
    "events": ["competitor.rating_change", "competitor.review_surge", "alert.created"],
    "active": true,
    "updatedAt": "2026-05-01T12:30:00Z"
  }
}

DELETE /webhooks/:id — Remove a webhook

curl -X DELETE https://app.neariq.io/api/v1/webhooks/wh_abc123 \
  -H "X-NearIQ-Key: niq_your_key_here"
{
  "deleted": true
}
Deleting a webhook is permanent. Pending deliveries for the endpoint are canceled.