Developers
REST API & webhooks
Use the REST API to read post status from your backend, and webhooks to get notified when a post goes live—without polling. Start in the dashboard, test with one click, then wire up production.
Local test code
Copy-paste a webhook listener and curl examples for your laptop on the Local test examples page.Recommended approach
Most integrations use webhooks for real-time updates and the REST API as a fallback or for backfills.
Configure
Create an API key and save a webhook URL in Dashboard → API Keys.
Test
Send a test webhook, verify your signature handler, then curl a REST endpoint.
Ship
Listen for post.sent in production; use GET /posts/[id] only when you need a refresh.
Poll vs push
Webhooks (push) — Prostnow POSTs to your server when a post is first published. Best for automation that should react immediately. REST (pull) — Your server callsGET /api/v1/posts/[id] when you need to double-check status. Use both: webhooks for speed, REST for recovery after downtime.Testing your integration
Follow these steps in order before connecting production traffic.
- 1Open Dashboard → API Keys and create a key. Copy pn_live_… into your server env (never the browser).
- 2Enter your webhook endpoint URL and click Save. Copy the whsec_… signing secret — you need it to verify incoming POSTs.
- 3Click Send test webhook. Your endpoint should receive a sample post.sent payload with test: true. Return HTTP 2xx.
- 4Implement signature verification (see below), then re-run the test until your handler accepts the event.
- 5Call GET /api/v1/posts?limit=5 with your API key to confirm REST auth works.
- 6Publish a real post from the dashboard. Confirm you receive a live post.sent webhook (no test flag) or poll GET /posts/[id] until sent is true.
All setup and testing controls live in Dashboard → API Keys. The test button sends a signed payload identical to production, except test: true and a fake post id.
Local development
Usehttp://localhost:… or http://127.0.0.1:… as your webhook URL, or expose a tunnel (ngrok, Cloudflare Tunnel, etc.) if you need a public https URL. Save the URL in the dashboard, then use Send test webhook.Authentication (REST API)
API keys are separate from webhook signing secrets.
| Credential | Prefix | Used for |
|---|---|---|
| API key | pn_live_… | Outbound REST calls from your server |
| Signing secret | whsec_… | Verifying inbound webhook POSTs from Prostnow |
Send the API key on every REST request:
Authorization: Bearer pn_live_…Base URL (adjust host for local dev):
https://www.prostnow.com/api/v1Webhooks
One POST per post, when it first reaches a published state.
Configure the endpoint in Dashboard → API Keys. Production URLs must use https://.
Request
- Method
POST, bodyapplication/json X-Prostnow-Timestamp— Unix msX-Prostnow-Signature—t={timestamp},v1={hex}
Payload shape
{
"type": "post.sent",
"test": true,
"occurredAt": "2026-06-06T12:00:00.000Z",
"post": {
"id": "prostnow_webhook_test",
"title": "Webhook test",
"status": "posted",
"sent": true,
"failed": false,
"displayStatus": "posted",
"platforms": ["instagram", "x"],
"platformViewUrls": null,
"content": "…",
"mediaUrls": [],
"scheduledFor": null,
"createdAt": "…",
"updatedAt": "…"
}
}Live events omit test and use the real Convex post id. The post object matches REST API fields.
Verify the signature
Parse t from X-Prostnow-Signature, then compute HMAC-SHA256 of {timestamp}.{raw request body} using your whsec_… secret. Compare the hex digest to v1.
// Node.js example (use the raw body string, not re-serialized JSON)
import crypto from "crypto";
function verifyProstnowWebhook(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.trim().split("="))
);
const timestamp = parts.t;
const expected = parts.v1;
if (!timestamp || !expected) return false;
const signed = `${timestamp}.${rawBody}`;
const digest = crypto
.createHmac("sha256", secret)
.update(signed)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(digest, "hex"),
Buffer.from(expected, "hex")
);
}Handler checklist
Read the raw body before JSON parsing · Reject if signature invalid · Return2xx quickly · Ignore events where test: true in production logs if you only want live posts · Rotate the signing secret from the dashboard if compromisedGET post status
Fetch one post by id — useful after downtime or when webhooks are disabled.
GET https://www.prostnow.com/api/v1/posts/{postId}Use the post id from the dashboard URL, webhook payload, or list response. Important fields:
sent—truewhen publishedfailed—trueif publishing faileddisplayStatus—posted,scheduled, ordraftplatformViewUrls— public URLs per platform after publish
Example:
curl -s \
-H "Authorization: Bearer $PROSTNOW_API_KEY" \
"https://www.prostnow.com/api/v1/posts/j572abc123..."List posts
Newest first — handy for finding ids during initial REST testing.
GET https://www.prostnow.com/api/v1/posts?limit=20Optional limit (1–50, default 20). Response: { "posts": [ … ] }.
curl -s \
-H "Authorization: Bearer $PROSTNOW_API_KEY" \
"https://www.prostnow.com/api/v1/posts?limit=5"Errors
- 401 — missing API key, wrong prefix, or revoked key
- 404 — post id not found or not owned by your account
- Webhook delivery fails if your endpoint returns non-2xx or times out (15s). Fix the handler, then use Send test webhook to retry.
Quick reference
- Dashboard
- /dashboard/api-keys — keys, webhook URL, test button
- REST auth
- Authorization: Bearer pn_live_…
- Webhook secret
- whsec_… (HMAC-SHA256)
- Event type
- post.sent
- Test flag
- test: true on dashboard test events only