Webhooks
Receive real-time notifications when onboarding events happen.
Starter plan and above.
Webhooks let you receive HTTP POST requests when events happen in Portico — a client submits a response, completes an onboarding, signs a document, or makes a payment. Use them to trigger workflows in your own systems or connect to Zapier.
Setup
- Go to Settings > Connect Services.
- Enter a public HTTPS URL in the webhook field.
- Click Save.
Portico generates a webhook_secret on first setup. Copy it immediately — you'll need it to verify signatures. Clearing the URL clears the secret.
Events
| Event | Fires when |
|---|---|
onboarding.sent | You send an onboarding to a client |
onboarding.completed | A client completes all required fields and the onboarding is marked complete |
response.submitted | A client submits or resubmits a field response |
response.approval_revoked | An admin revokes a previously approved response |
signature.signed | A client signs a signature field |
payment.succeeded | A client's payment is successfully processed through Stripe |
The response.approval_revoked event is only sent to your team webhook, not to Zapier subscriptions.
Payload format
Every webhook POST has this structure:
{
"event": "onboarding.completed",
"timestamp": "2026-03-15T10:30:00.000Z",
"data": {
"onboarding_id": "uuid"
}
}
The data object varies by event:
| Event | Data fields |
|---|---|
onboarding.sent | onboarding_id |
onboarding.completed | onboarding_id |
response.submitted | onboarding_id, field_id, value |
response.approval_revoked | onboarding_id, field_id |
signature.signed | onboarding_id, field_id, value |
payment.succeeded | onboarding_id, amount_cents, payment_intent_id |
Headers
Every webhook request includes these headers:
Content-Type: application/json
X-Portico-Event: onboarding.completed
X-Portico-Signature: a1b2c3d4...
X-Portico-Event is always present. X-Portico-Signature is included when a webhook secret is configured.
Verifying signatures
Verify that a webhook came from Portico by computing the HMAC-SHA256 signature of the raw request body and comparing it to the X-Portico-Signature header.
Node.js example:
import crypto from "crypto";
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Python example:
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Use a constant-time comparison (as shown above) to prevent timing attacks.
Delivery and retries
- Timeout — Portico waits up to 10 seconds for your endpoint to respond.
- Retries — if the first attempt fails or times out, Portico retries once after 5 seconds.
- No further retries — if both attempts fail, the delivery is logged and dropped. Portico does not queue or retry beyond the second attempt.
- Non-blocking — webhook delivery never delays the action that triggered it. Your client's experience is unaffected if your endpoint is slow or down.
Your endpoint should return a 2xx status code to acknowledge receipt. The response body is ignored.
Zapier integration
Instead of handling webhooks yourself, you can connect events to Zapier. Zapier subscriptions receive the same payload format and are managed through the API or directly in Zapier.
Zapier subscriptions support 5 events: onboarding.completed, onboarding.sent, response.submitted, signature.signed, and payment.succeeded.
Common Zapier workflows
- Onboarding completed — create a project in Asana, Notion, or Monday.
- Client submitted — post a notification to a Slack channel.
- Payment succeeded — add a row to a Google Sheet or log it in QuickBooks.
- Signature signed — send a confirmation email via Gmail or Mailchimp.
Testing webhooks
During development, use a tool like webhook.site or ngrok to inspect payloads before pointing the webhook at your production endpoint.
To trigger a test event, send an onboarding to yourself and complete it — this fires onboarding.sent, response.submitted, and onboarding.completed in sequence.