# Webhooks

**The Serply REST API does not currently support outbound webhooks.** There is
no endpoint for registering a callback URL, and the API does not push events to
your application.

Serply's endpoints are synchronous: you make a request, and the results come
back in that same response. There is no job queue to be notified about and no
`search.completed` event, because the search has already completed by the time
the call returns.

If you need to be notified when something on the web changes, you build that
loop on your side. The pattern is below.

## Polling instead

Run your query on a schedule, compare against what you saw last time, and act
only on the difference:

```python
import json
import time
from pathlib import Path

import requests

HEADERS = {
    'X-Api-Key': 'YOUR_API_KEY',
    'X-Proxy-Location': 'US',  # pin the region - see the warning below
}
STATE = Path('seen.json')


def search(query, num=10):
    r = requests.get(
        f'https://api.serply.io/v1/search/q={query}&num={num}',
        headers=HEADERS,
        timeout=15,
    )
    r.raise_for_status()
    return r.json().get('results', [])


def check(query):
    seen = set(json.loads(STATE.read_text())) if STATE.exists() else set()

    new = [item for item in search(query) if item['link'] not in seen]

    if new:
        notify(new)  # Slack, email, a database write - whatever you need

    STATE.write_text(json.dumps(sorted(seen | {i['link'] for i in new})))
    return new


while True:
    check('your+query+here')
    time.sleep(900)  # every 15 minutes
```

Storing the state is the part that matters. Without it you re-alert on the same
results every cycle, and the notifications stop being read.

## Always pin `X-Proxy-Location` when monitoring

This is the detail that breaks most polling loops, and it is not obvious.

If you omit `X-Proxy-Location`, your request is served from whichever proxy is
available, and the response comes back with `device_region` set to an empty
string. Most consecutive calls then return the same results - until one is
served from a different country, at which point you get an entirely different
SERP.

In testing, six identical queries three seconds apart returned the same nine
results five times, and on the sixth returned a German result set sharing
**zero** links with the previous five. A monitor built on that will announce
that 100% of its results are new, then revert on the next cycle.

Pinning the header removes the problem completely. The same six-run test with
`X-Proxy-Location: US` returned identical results every time:

```python
HEADERS = {
    'X-Api-Key': 'YOUR_API_KEY',
    'X-Proxy-Location': 'US',
}
```

Any change in results is then a real change in the SERP, which is the only
thing worth alerting on. See [Google Search](/docs/resources/google-search) for
the full list of accepted regions.

## Why polling is cheaper than it sounds

Credits are consumed per **successful, uncached** request. Cached responses cost
nothing, and most endpoints cache for several minutes - Reddit endpoints cache
for 10 minutes, and the Reddit comments response tells you directly with a
`cached` field.

The practical effect is that polling faster than the cache window does not cost
more. A monitor that checks every minute pays for roughly the same number of
credits as one that checks every ten, because the intervening calls are served
from cache.

That also means there is little value in polling very aggressively: you will
receive the cached response until the window expires. Match your interval to
how quickly the underlying data actually changes - for most search and news
monitoring, 15 minutes to an hour is plenty.

## Handling failures in a long-running loop

A polling loop runs unattended, so it needs to survive the transient errors a
one-off script can ignore:

- `502` means the upstream fetch or parse failed. It is transient - retry before
  treating it as an outage.
- A `200` response with an empty `results` array is usually the same transient
  failure rather than a genuine "no results". Retry once or twice before
  recording that state, or your loop will alert on results "disappearing" and
  then "reappearing" on the next cycle.
- `429` means you hit the rate limit. Back off and retry.

See the [Errors](/docs/guides/errors) guide for the full error format.

## If you need push delivery

Some use cases genuinely need push rather than poll - high query volumes, or
alerting where a 15-minute delay is too long. If that describes your setup,
contact [support](/support) and describe the workload; requirements like this
help prioritize what gets built.
