PRAW 8: the production setup for buyer-intent Reddit alerts
A production walkthrough for PRAW 8: poll subs for buyer-intent keywords, pipe matches to Slack, and dodge the rate-limit and auth traps that broke v7 scripts.
PRAW 8 shipped in November 2026 with a stricter auth flow, a rewritten rate-limit handler, and a few breaking changes that will silently kill any v7 script you leave running. If you use Reddit as a lead source, that's your monitoring cron dying at 3am and you finding out on Monday.
This is the setup I run in production: a small PRAW 8 poller that watches a handful of subreddits, filters submissions and comments for buying-intent phrases, and posts the matches to a Slack channel with enough context to reply in under a minute.
What changed in PRAW 8 that will break your v7 script
The PRAW 8 changelog is worth reading in full, but three things matter for a lead-monitoring workflow:
- Script-app auth is stricter. PRAW 8 refuses to start if your
user_agentstring is generic (python:bot:1.0won't fly). Reddit's API team has been aggressive about identifying and throttling anonymous-looking traffic since the May 2026 scraper crackdown, and PRAW now enforces the format they expect at client init. - The rate-limit backoff is different. v7 would sleep in short bursts. v8 sleeps once, longer, based on the
x-ratelimit-resetheader. If your script assumed a burst pattern, your log will now look frozen for 300+ seconds. SubredditStreamyields differently on cold start. By default it now skips historical items and starts from "now". If you were relying on backfill to seed a database on first run, you have to passskip_existing=Falseexplicitly.
Migrating from v7 is mostly a config-file and user-agent job. If you've never touched PRAW before, install fresh and skip the migration section entirely.
The architecture
One process, three responsibilities: pull, filter, notify. Slack is the notification target because it's where founders already read things, but the same pattern works for Discord, email digests, or a database.
Rendering diagram…
Keep it one process. A queue + worker split is tempting and premature. Reddit's rate limits are per-OAuth-app, so parallelism doesn't buy you speed, it just fragments your budget.
Step 1: register the app and set up praw.ini
Go to https://www.reddit.com/prefs/apps and create a script app. You need the client ID (under the app name), the client secret, and a Reddit account that will act as the script's identity. Use a dedicated account, not your personal one.
Create praw.ini in your project directory:
[buyerbot]
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
username=your_bot_account
password=your_bot_password
user_agent=linux:com.yourcompany.buyerbot:v1.0 (by /u/your_bot_account)
The user_agent format matters. PRAW 8 checks for a platform, a reverse-domain identifier, a version, and an operator handle. Skip any of those and the client raises on the first request.
Step 2: the poller
import praw
import re
import time
import requests
reddit = praw.Reddit("buyerbot")
SUBS = "SaaS+Entrepreneur+startups+webdev+nocode"
INTENT_PATTERNS = [
r"\b(any|what|which|recommend|suggest)\s+(tool|app|service|product)",
r"\b(looking for|need|searching for)\b.*\b(that|which|to)\b",
r"\b(alternative to|instead of|replacement for)\b",
r"\b(is there a|does anyone know a)\b",
r"\bwilling to pay\b",
]
COMPILED = [re.compile(p, re.IGNORECASE) for p in INTENT_PATTERNS]
SLACK_WEBHOOK = "https://hooks.slack.com/services/XXX/YYY/ZZZ"
seen = set()
def matches_intent(text):
return any(p.search(text) for p in COMPILED)
def notify(item, kind):
if item.id in seen:
return
seen.add(item.id)
text = item.title if kind == "post" else item.body
payload = {
"text": f"*{kind}* in r/{item.subreddit.display_name}\n"
f"<{item.permalink and 'https://reddit.com' + item.permalink}|link>\n"
f"> {text[:300]}"
}
requests.post(SLACK_WEBHOOK, json=payload, timeout=5)
def run():
subreddit = reddit.subreddit(SUBS)
while True:
try:
for submission in subreddit.stream.submissions(skip_existing=True, pause_after=0):
if submission is None:
break
if matches_intent(f"{submission.title} {submission.selftext}"):
notify(submission, "post")
for comment in subreddit.stream.comments(skip_existing=True, pause_after=0):
if comment is None:
break
if matches_intent(comment.body):
notify(comment, "comment")
except Exception as e:
print(f"error: {e}, sleeping 60s")
time.sleep(60)
if __name__ == "__main__":
run()
A few things worth calling out:
pause_after=0makes each stream yieldNonewhen it runs out of new items so you can alternate between submissions and comments in one process instead of blocking on one forever.skip_existing=Truematches the v7 default behavior. Turn it off only if you're doing a backfill.- The
seenset is in-memory; for anything running longer than a day, swap it for a SQLite table keyed onitem.id.
Step 3: buyer-intent filters that don't drown you
The regex list above is the starting point. Every real deployment ends up with a two-layer filter: a broad regex sieve, then a tighter contextual check. Broad regex alone matches too much. Full-text embedding search matches too little of the noise you'd actually want to catch.
What works in practice:
- Positive patterns: "looking for a tool that…", "any alternatives to X", "willing to pay for", "does anyone use…", "switched from X to Y".
- Negative patterns to strip: "I built", "launched", "my SaaS" (these are people announcing, not asking).
- Score threshold: on posts older than 30 minutes, require score ≥ 2 before alerting. Filters out immediate mod removals and posts that landed with zero engagement.
If you want to go further, add a cheap LLM call as the second stage. Pass the matched text to a model with a one-shot classification prompt ("is this person actively looking for a product to buy: yes/no"). Keep the model call gated behind the regex match so you're not paying for every post in a busy sub.
Step 4: the rate-limit reality
PRAW 8 handles the 429s for you, but you still have a real budget: 100 queries per minute per OAuth app for authenticated scripts. subreddit.stream uses .new() under the hood on a fixed interval, and each .new() on a multireddit counts as one request. Five subs in a +-joined multireddit is one request, not five. That's the trick that keeps this workflow cheap.
Things that will burn your budget:
- Iterating comments on every matched post to "enrich" the alert. Don't. Send the permalink, open it when the alert fires.
- Fetching
author.link_karmafor every hit. You almost never actually use it in the Slack message. - Running two streams (comments + submissions) as separate processes on the same OAuth app. They fight for the same 100/min bucket.
Step 5: running it in production
systemd unit, one file, restart on failure:
[Unit]
Description=Reddit buyer-intent poller
After=network.target
[Service]
Type=simple
User=buyerbot
WorkingDirectory=/opt/buyerbot
ExecStart=/opt/buyerbot/venv/bin/python poller.py
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
Log to stdout, let journald collect it. If you want a fancier setup, ship the logs to Loki or Datadog, but for a single-founder monitor this is enough.
One thing to build in from day one: an ack workflow. When you reply to a lead, click a Slack reaction (✅) on the alert. That's your record of what you actioned. Once a week, look at the un-acked alerts and figure out whether the filter is too loose or you're just missing days.
When rolling your own stops being worth it
A hand-rolled PRAW poller is 50 lines of code, one server, and about an hour of setup. Great for one product, one niche, a handful of subs.
It stops scaling when you want (a) multiple products with different keyword sets, (b) full comment-thread context on matches, (c) author history to filter out obvious low-quality accounts, or (d) DM-ready message drafts attached to each alert. At that point you're building a small product, not a script, and Bazzly does that watching for you with the enrichment already wired in.
Either way, PRAW 8 is the right foundation. The v7 scripts on GitHub will keep working for another few months, but the auth format is going to bite eventually. Rewrite the user-agent, pin praw>=8.0.2, and add skip_existing explicitly wherever you use a stream. That gets you through the next twelve months without a 3am pager.
Related reading
Reddit JSON API: the no-auth workflow for buyer-thread alerts
Append .json to any Reddit URL to pull posts and comments without OAuth, then pipe buying-intent threads straight into a Slack channel.
Reddit's scraper crackdown: what the May 2026 rules mean for marketers
Reddit's May 2026 anti-scraper policy tightens rules on unverified bots and data pulls. Here's what actually changes for founders running Reddit growth.
How to spot 'first dollar' threads on r/SaaS and reply without getting flagged
A repeatable tactic for finding 'first revenue' celebration posts on r/SaaS and writing replies that build credibility instead of getting auto-removed.