Available events
Portal.io fires webhooks for the following event types:| Event name | Triggered when |
|---|---|
Proposal Build Status Changed | The AI Builder finishes (or fails) building a proposal from an approved outline. |
Proposal Outline Status Changed | The AI outline generation status changes — for example, when the outline moves from Generating to Completed. |
Proposal Status Changed | A proposal’s status changes — for example, from Draft to Sent, or from Sent to Approved. |
| Zapier trigger | Fires when |
|---|---|
| Person Modification | A contact record is created or updated. |
| Payment Status Change | A payment’s status changes. |
| Proposal Status Change | A proposal’s status changes. |
| Order Status Change | An order’s status changes. |
Subscribing to webhooks
Create a webhook subscription by callingPOST /public/webhook/subscribe with your endpoint URL and the events you want to receive.
subscriptionId— use this to update or delete the subscription latersecretKey— use this to verify that incoming webhook requests are genuinely from Portal.io (see Verifying webhook signatures)url,description,enabled, andevents
Managing subscriptions
| Operation | Endpoint |
|---|---|
| Create subscription | POST /public/webhook/subscribe |
| List all subscriptions | GET /public/webhook/subscriptions |
| Update a subscription | POST /public/webhook/subscription/{SubscriptionId} |
| Delete a subscription | DELETE /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-Signatureheader for verification (see below) - A JSON body describing the event
| Attempt | Delay after previous failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
Verifying webhook signatures
Every webhook request includes anX-Webhook-Signature header so you can confirm the request came from Portal.io and has not been tampered with.
Header format:
t— Unix timestamp of when the webhook was generatedv1— HMAC-SHA256 signature
t is 1710000000 and the request body is {"id":10025,"number":4123,...}, the signed message is:
Read the raw request body
Use the raw bytes exactly as received — do not parse or normalize the JSON before verifying.
Compute the expected signature
Run
HMAC_SHA256(secretKey, signedMessage) using your subscription’s secretKey.