# Is there a webhook for new Instagram or TikTok posts?

Instagram. Checked September 2026.

No. Instagram and TikTok send webhooks only to apps that an account owner authorized, and only about that account. For a public account you do not own, nobody will call you. Openhandle does not offer webhooks either. It reads when you ask.

The working pattern is a poll. Read the profile posts endpoint on a schedule, compare post IDs with what you stored, and treat every new ID as an event. One page per check is enough, because the feed is newest first.

## GET /v1/instagram/profiles/@username/posts?freshness=live

```bash
curl "https://api.openhandle.dev/v1/instagram/profiles/@northstar_forge_test/posts?freshness=live" \
  -H "Authorization: Bearer $OPENHANDLE_API_KEY"
```

```ts
import { OpenHandle } from '@openhandle/sdk';

const openhandle = new OpenHandle({ apiKey: process.env.OPENHANDLE_API_KEY! });

// Run every 15 minutes. One page, newest first.
const page = await openhandle.instagram.profile('northstar_forge_test').posts.list({ freshness: 'live' });
for (const post of page.data) {
    if (await seen(post.id)) break;
    await onNewPost(post);
}
```

200 OK:

```json
{
  "platform": "instagram",
  "resource": "post",
  "capturedAt": "2026-09-18T11:45:00Z",
  "source": "live",
  "data": [
    {
      "id": "910100000001",
      "createdAt": "2026-08-09T15:30:00Z",
      "metrics": { "likes": 840, "comments": 5 }
    }
  ],
  "meta": { "cursors": { "next": "b3BhcXVlLWN1cnNvcg" } }
}
```

Trimmed to one post. Because the feed is newest first, you can stop at the first ID you already know. The example is a Test profile.

## Pick a poll interval

| You need | Interval | Cost per account per day |
|---|---|---|
| Same-day | Every 6 hours | 4 live reads, $0.0025 each |
| Within the hour | Every 15 minutes | 96 live reads, $0.0025 each |
| A daily digest | Once a day | 1 read, $0.0005 on a cache hit or $0.0025 live |

Use `freshness=live` for a poll. A cache hit could return the same page you saw last time. The TikTok profile posts endpoint works the same way with the same shape.

## Make it robust

- **Key on `id`, not position.** Pinned posts sit at the top of an Instagram feed. A new post can appear below them. Compare IDs, do not assume the first item is newest.
- **Store `capturedAt`.** It is when the platform showed the post. Your event time should be that, not your cron time.
- **Handle a private flip.** An account can go private between polls. `PROFILE_PRIVATE` is an event too. Store it and stop polling until you decide to retry.
- **Spread the schedule.** A thousand accounts polled at the same minute hit your own rate limit. Stagger them across the interval.

## When this does not work

These answers are events. Store them, do not retry them.

| Situation | Status | Code |
|---|---|---|
| The account went private | 403 | `PROFILE_PRIVATE` |
| The account is deleted or renamed without an ID lookup | 404 | `PROFILE_NOT_FOUND` |
| You passed your own per-second limit | 429 | `RATE_LIMITED` |

```json
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Retry after the Retry-After header.",
    "requestId": "req_01j8…",
    "retryable": true
  }
}
```

Rate limits are per key, 10 requests per second, and never billed. Honor `Retry-After`. Failed requests are never billed.

## Related

- [Brand monitoring use case](https://openhandle.dev/use-cases/brand-monitoring): Watchlists built on scheduled reads.
- [Freshness and caching](https://openhandle.dev/docs/concepts/freshness-and-caching): Live, or up to 24 hours, 7 days, or 30 days old.
- [Rate limits](https://openhandle.dev/docs/rate-limits): Quota headers and retry behavior.
