Skip to main content
Webhooks let your system react to events in Portal.io without polling the API continuously. When a subscribed event occurs — such as a proposal being approved or an AI build completing — Portal.io sends an HTTP POST request to your configured endpoint with a JSON payload describing the event.
Use webhook events alongside targeted API calls to keep your system in sync. For example, listen for Proposal Status Changed to trigger a workflow, then call GET /public/proposals/{ProposalId} to fetch the full updated proposal details.

Available events

Portal.io fires webhooks for the following event types:
Event nameTriggered when
Proposal Build Status ChangedThe AI Builder finishes (or fails) building a proposal from an approved outline.
Proposal Outline Status ChangedThe AI outline generation status changes — for example, when the outline moves from Generating to Completed.
Proposal Status ChangedA proposal’s status changes — for example, from Draft to Sent, or from Sent to Approved.
In addition, Portal.io supports the following Zapier trigger events, which fire on the same underlying data changes:
Zapier triggerFires when
Person ModificationA contact record is created or updated.
Payment Status ChangeA payment’s status changes.
Proposal Status ChangeA proposal’s status changes.
Order Status ChangeAn order’s status changes.

Subscribing to webhooks

Create a webhook subscription by calling POST /public/webhook/subscribe with your endpoint URL and the events you want to receive.
curl -X POST https://api.portal.io/public/webhook/subscribe \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d Url=https://yourapp.example.com/portal/webhooks \
  -d Description="Production+webhook" \
  -d Events=ProposalStatusChanged
The response includes:
  • subscriptionId — use this to update or delete the subscription later
  • secretKey — use this to verify that incoming webhook requests are genuinely from Portal.io (see Verifying webhook signatures)
  • url, description, enabled, and events
Your endpoint URL must use HTTPS and must be publicly routable. Portal.io rejects private, loopback, and link-local addresses.

Managing subscriptions

OperationEndpoint
Create subscriptionPOST /public/webhook/subscribe
List all subscriptionsGET /public/webhook/subscriptions
Update a subscriptionPOST /public/webhook/subscription/{SubscriptionId}
Delete a subscriptionDELETE /public/webhook/unsubscribe/{SubscriptionId}

What your endpoint receives

When an event fires, Portal.io sends an HTTP POST to your endpoint with:
  • Content-Type: application/json
  • An X-Webhook-Signature header for verification (see below)
  • A JSON body describing the event
Your endpoint must respond with a 2xx HTTP status code to acknowledge receipt. If Portal.io does not receive a 2xx response, it retries the delivery on this schedule:
AttemptDelay after previous failure
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 hours

Verifying webhook signatures

Every webhook request includes an X-Webhook-Signature header so you can confirm the request came from Portal.io and has not been tampered with. Header format:
X-Webhook-Signature: t=1710000000,v1=5f2b3e7a9d1c4f8e6b2a3c9d7f4e1a6b5c3d2f7e9a1b4c6d8e0f2a3b5c7d9e1
Where:
  • t — Unix timestamp of when the webhook was generated
  • v1 — HMAC-SHA256 signature
Signature calculation:
signature = HMAC_SHA256(secretKey, t + "." + requestBody)
For example, if t is 1710000000 and the request body is {"id":10025,"number":4123,...}, the signed message is:
1710000000.{"id":10025,"number":4123,...}
Verification steps:
1

Read the raw request body

Use the raw bytes exactly as received — do not parse or normalize the JSON before verifying.
2

Extract the signature header values

Parse X-Webhook-Signature to extract t and v1.
3

Rebuild the signed message

Concatenate t, a literal ., and the raw request body.
4

Compute the expected signature

Run HMAC_SHA256(secretKey, signedMessage) using your subscription’s secretKey.
5

Compare and validate

Compare your computed value with v1. Reject the request if they do not match, or if t is more than 5 minutes old.

Zapier integration

Portal.io supports Zapier natively. If you are building automations with Zapier rather than a direct integration, the same underlying events — person modifications, payment status changes, proposal status changes, and order status changes — are available as Zapier triggers. Connect the Portal.io app in Zapier to configure these without writing any code.