Prowlo — Reddit marketing tool for SaaSProwlo
Log In
Help

Webhooks: Receive Scored Opportunities Anywhere

Prowlo can fire HMAC-signed JSON payloads at any URL whenever a new batch of scored Reddit opportunities is ready. Pipe them into Slack via an incoming hook, into Zapier or Make for downstream automation, into your CRM, or into a homegrown service.

What you get

  • HMAC-SHA256 signed payloads. Every request includes an X-Prowlo-Signature header you verify against your per-webhook signing secret.
  • Automatic retries with exponential backoff. Non-2xx responses re-queue. The delivery log shows every attempt.
  • Test endpoint + manual replay. Fire a sample payload immediately on creation; replay any failed delivery from the dashboard.
  • Auto-disable on consecutive failures. If your endpoint stays broken, the webhook auto-disables with a clear reason instead of silently dropping signal.
  • Available on every plan. Webhooks are not gated to enterprise. Same primitives on the $23/mo Starter as on Growth.

Create a webhook

1

Open the Webhooks page

In your Prowlo dashboard, go to Notifications → Webhooks. Click Add webhook.

2

Configure the endpoint

Fill in three fields:

  • URL — your endpoint that will receive POST requests. Must be HTTPS in production.
  • Description — optional, for your own bookkeeping (e.g. "Slack #leads", "Zapier sheet sync").
  • Events — pick which events to subscribe to. Currently curation.batch.ready; more events on the roadmap.
3

Save the signing secret

On creation, Prowlo returns a one-time signing secret. Store it in your secrets manager — you will use it to verify incoming requests. The secret is shown only once; if you lose it, rotate it from the same page (one click).

4

Click Test

Use the Test button to fire a sample payload at your URL. The delivery log will show whether your endpoint accepted it and what status code it returned.

Payload structure

Every payload is JSON with this shape:

{
  "event": "curation.batch.ready",
  "event_id": "evt_01H8XYZ...",
  "delivered_at": "2026-04-29T19:12:33.421Z",
  "organization_id": "org_01H...",
  "data": {
    "batch_id": "batch_01H...",
    "opportunities": [
      {
        "id": "opp_01H...",
        "post_id": "abc123",
        "subreddit": "r/SaaS",
        "title": "Anyone know a good tool for finding Reddit leads?",
        "url": "https://reddit.com/r/SaaS/comments/abc123",
        "scores": {
          "fit": 95,
          "relevance": 0.63,
          "survival": "MEDIUM"
        },
        "engagement_brief": {
          "verdict": "Safe to engage",
          "approach": "Answer first, then mention soft",
          "warnings": ["No direct links — mods remove them"]
        }
      }
    ]
  }
}

The exact shape of data depends on the event type. Always check the top-level event field in your handler before parsingdata.

Verify the HMAC signature

Prowlo signs every payload with HMAC-SHA256 over the raw request body, using your per-webhook signing secret. The signature ships in the X-Prowlo-Signature header, hex-encoded.

Always verify in constant time before processing the payload.

Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.PROWLO_WEBHOOK_SECRET;

// IMPORTANT: capture the raw body for signature verification
app.use('/prowlo', express.raw({ type: 'application/json' }));

app.post('/prowlo', (req, res) => {
  const signature = req.header('X-Prowlo-Signature');
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(req.body)         // raw Buffer, not parsed JSON
    .digest('hex');

  const ok = signature && crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex'),
  );
  if (!ok) return res.status(401).send('bad signature');

  const payload = JSON.parse(req.body.toString('utf8'));
  // ... handle payload.event, payload.data ...

  res.status(200).send('ok');
});

Python (Flask)

import hashlib
import hmac
import os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ['PROWLO_WEBHOOK_SECRET'].encode('utf-8')

@app.post('/prowlo')
def prowlo_webhook():
    signature = request.headers.get('X-Prowlo-Signature', '')
    expected = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)
    payload = request.get_json(force=True)
    # ... handle payload['event'], payload['data'] ...
    return ('', 200)

curl (manual test)

# Compute the expected signature for a known payload
echo -n '{"event":"curation.batch.ready","event_id":"evt_test"}' \
  | openssl dgst -sha256 -hmac "$PROWLO_WEBHOOK_SECRET" \
  | awk '{print $2}'

Use this to verify your handler matches what Prowlo would compute on the same body.

Retries, replay, and auto-disable

  • Retry policy. Any non-2xx response triggers a retry with exponential backoff. Successive failures get progressively longer delays.
  • Delivery log. Every attempt is logged with status code, response body snippet, and error message. Filter by webhook or status. Found in the dashboard at Notifications → Webhooks → Deliveries.
  • Manual replay. Click Redeliver on any failed entry to re-fire the original payload. Useful after deploying a fix to your handler.
  • Auto-disable. If your endpoint fails consecutively past the failure threshold, the webhook auto-disables with a clear reason (timeouts, 5xx, signature mismatch, etc.). Re-enable from the Webhooks page once your endpoint is fixed.
  • Idempotency. The event_id field is stable across retries. Use it as your idempotency key so a replay does not duplicate downstream side effects.

Common destinations

Slack

Easiest path: in Slack, create an incoming webhook for the channel you want opportunities to post to. Paste that URL into Prowlo as the webhook destination. Slack will format the JSON payload and post the message into the channel automatically.

Zapier / Make / n8n

All three platforms support generic webhook triggers as a first-class integration. Create a "Catch Webhook" trigger in Zapier (or equivalent in Make/n8n), copy the URL it gives you, paste it into Prowlo. Downstream you can route opportunities to a CRM, spreadsheet, or chat.

Discord

Discord channels expose a webhook URL natively (Channel Settings → Integrations → Webhooks → New Webhook). Same flow as Slack — paste the URL into Prowlo and Discord posts the JSON into the channel.

Telegram

Create a Telegram bot via BotFather, get the bot token, and use a tiny adapter (Cloudflare Worker, Vercel Edge function, or a one-line Zap) to forward Prowlo's payload to https://api.telegram.org/bot<token>/sendMessage. ~10 lines of code total.

Troubleshooting

My endpoint never receives the test payload

  1. Confirm the URL is publicly reachable (use curl -X POST from a different network).
  2. Check your firewall allows POST from Prowlo's outbound IPs.
  3. Verify your TLS certificate is valid (we don't accept self-signed in production).
  4. Check the delivery log — if attempts are showing as failed, the response status code and error message will tell you what your handler returned.

Signature verification fails locally but the payload is valid

  1. You must compute the HMAC over the raw bytes of the request body, not over a re-serialized JSON object. Re-serialization can re-order keys or add/remove whitespace, breaking the signature.
  2. In Express, that means express.raw() middleware on the route, NOT express.json().
  3. Use constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest) — not ===.

My webhook auto-disabled. How do I re-enable?

Go to Notifications → Webhooks. The disabled webhook will show a reason (e.g. "5 consecutive 5xx responses"). Fix the underlying issue, then click Re-enable. Past failed deliveries can be replayed individually from the delivery log.