Webhooks

Configure an HTTPS endpoint to receive POST notifications for 9 event types, verify payloads with HMAC-SHA256 signatures, and handle automatic retries.

Starter plan and above.

Webhooks send HTTP POST requests to your server when events happen in Portico. Examples include a client submitting a response, completing an onboarding, signing a document, or making a payment. Use them to trigger workflows in your own systems.

Setup

  1. Go to Settings > Integrations.
  2. Enter a public HTTPS URL in the webhook field.
  3. Click Save.

Portico generates a webhook_secret on first setup. Copy it immediately — you need it to verify signatures. Clearing the URL clears the secret.

Events

EventFires when
onboarding.sentYou send an onboarding to a client.
onboarding.completedA client completes all required fields and the onboarding is marked complete.
onboarding.startedA client opens the onboarding link for the first time.
onboarding.overdueAn onboarding passes its due date without completion.
response.submittedA client submits or resubmits a field response.
response.approval_revokedAn admin revokes a previously approved response.
signature.signedA client signs a signature field.
payment.succeededA client's payment is successfully processed through Stripe.
file.uploadedA client uploads a file to a file upload field.

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:

EventData fields
onboarding.sentonboarding_id
onboarding.completedonboarding_id
onboarding.startedonboarding_id
onboarding.overdueonboarding_id
response.submittedonboarding_id, field_id, value
response.approval_revokedonboarding_id, field_id
signature.signedonboarding_id, field_id, value
payment.succeededonboarding_id, amount_cents, payment_intent_id
file.uploadedonboarding_id, field_id, file_name

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.

Because Portico retries only once, design your endpoint to be reliable. If you need guaranteed delivery, consider polling the Events API endpoint as a fallback to catch any missed webhooks.

Zapier alternative

If you prefer not to manage webhook endpoints yourself, use the Zapier integration instead. Zapier handles the subscription and delivery automatically.

Testing

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, onboarding.started, response.submitted, and onboarding.completed in sequence.

Frequently asked questions

How many webhook event types are available?
9 event types covering the full onboarding lifecycle: created, sent, opened, submitted, approved, rejected, completed, cancelled, and expired.
How are webhook payloads verified?
Every payload includes an HMAC-SHA256 signature in the header. Verify it against your webhook secret to confirm the request came from Portico.
What happens if my endpoint is down?
Portico retries failed deliveries automatically with exponential backoff for up to 72 hours.