Proofly Pro · Test mode
API & Webhooks
Trigger reference requests from your CRM, keep deal status in sync and record closed-won deals — without opening Proofly.
Authentication
Owners and billing-authorized admins create keys in Settings → Developers. Keys are shown once, stored only as a hash and can be revoked anytime. Call the API from your server — never from a browser or mobile app. Only test keys (prf_test_) are available while live payouts are disabled.
curl https://getproofly.com/api/public/v1/requests \
-H "Authorization: Bearer prf_test_..."Scopes: read:references · read:requests · write:requests · write:conversions. Limit: 60 requests per minute per key, reported in X-RateLimit-* headers.
Endpoints
Base URL: https://getproofly.com/api/public/v1
curl -X POST https://getproofly.com/api/public/v1/requests \
-H "Authorization: Bearer prf_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: deal-4821-request" \
-d '{"reference_ids":["<reference id>"],"prospect_name":"Acme Corp","pain_point":"Migration risk","request_type":"Virtual meeting","scheduled_at":"2026-11-03T17:00:00Z","meeting_url":"https://zoom.us/j/123","requested_by_email":"rep@yourcompany.com"}'Errors
Errors return { "error": { "code", "message" } }. 400 invalid input · 401 invalid or revoked key · 402 Pro required · 403 missing scope · 404 not found · 409/422 idempotency or business-rule conflicts · 429 rate limited.
Webhooks
Add HTTPS endpoints in Settings → Developers. Events: request.created, request.accepted, request.declined, request.expired, request.completed, proof.uploaded, conversion.recorded, payout.released. Failed deliveries retry with backoff for about a day; use the event ID to ignore duplicates. Payloads never include file links, bank or card details.
Each delivery has a Proofly-Signature: t=timestamp,v1=hex header — an HMAC-SHA256 of timestamp.body using your signing secret. Reject old timestamps.
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}