Skip to main content
When you create a webhook subscription, you specify which event types you want to receive. Portal.io delivers a signed HTTP POST payload to your registered HTTPS endpoint each time a subscribed event occurs. This page documents every available event type and the shape of its payload.
Your endpoint must respond with a 2xx status code to acknowledge receipt. If Portal.io does not receive a 2xx response, it retries the delivery on this schedule: 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours.

Verifying Webhook Signatures

Every delivery includes an X-Webhook-Signature header you should use to verify the request came from Portal.io:
X-Webhook-Signature: t=1710000000,v1=5f2b3e7a9d1c4f8e6b2a3c9d7f4e1a6b5c3d2f7e9a1b4c6d8e0f2a3b5c7d9e1
  • t — Unix timestamp when the webhook was generated.
  • v1 — HMAC-SHA256 hex digest.
Verification steps:
  1. Read the raw request body exactly as received.
  2. Extract t and v1 from the X-Webhook-Signature header.
  3. Rebuild the signed message: t + "." + rawBody.
  4. Compute HMAC_SHA256(secretKey, signedMessage).
  5. Compare your result to v1. Reject the request if they do not match.
  6. Reject the request if t is more than 5 minutes old.
The secretKey is the value returned in the secretKey field when you created the subscription.

Event Types

Event TypeTrigger
ProposalBuildStatusChangedAI proposal builder finishes or fails
ProposalOutlineStatusChangedAI outline generation completes or fails
ProposalStatusChangedA proposal’s status changes (e.g. Draft → Sent, Sent → Approved)
PersonModificationA contact record is created or updated
PaymentStatusChangeA payment’s status changes
OrderStatusChangeAn order’s status changes

ProposalBuildStatusChanged

Fired when the Portal.io AI builder finishes generating a proposal or encounters an error during generation. Subscribe to this event if you need to react when a build completes asynchronously. Event name: ProposalBuildStatusChanged

Payload Fields

FieldTypeDescription
idstringUnique webhook delivery identifier.
typestringAlways ProposalBuildStatusChanged.
createdstring (ISO 8601)Timestamp when the event was generated.
data.statusstringBuild result status (e.g. Completed, Failed).
data.proposal.idintegerID of the affected proposal.
data.proposal.createdDatestringISO 8601 timestamp when the proposal was created.
data.proposal.lastModifiedDatestringISO 8601 timestamp of the last modification.
data.proposal.financialSummaryobjectFull financial breakdown including parts, labor, fees, taxes, and totals.
{
  "id": "evt_build_abc123",
  "type": "ProposalBuildStatusChanged",
  "created": "2026-04-06T00:22:19Z",
  "data": {
    "status": "Completed",
    "proposal": {
      "id": 4123,
      "createdDate": "2026-04-05T18:00:00Z",
      "lastModifiedDate": "2026-04-06T00:22:19Z",
      "financialSummary": {
        "partsSubtotal": 5200.00,
        "partsTotal": 5200.00,
        "laborTotal": 800.00,
        "feeTotal": 0,
        "proposalSubtotal": 6000.00,
        "proposalTotal": 6450.00
      }
    }
  }
}

ProposalOutlineStatusChanged

Fired when Portal.io’s AI outline generation completes or fails. Useful for workflows that wait for an AI-generated scope before proceeding. Event name: ProposalOutlineStatusChanged

Payload Fields

FieldTypeDescription
idstringUnique webhook delivery identifier.
typestringAlways ProposalOutlineStatusChanged.
createdstring (ISO 8601)Timestamp when the event was generated.
data.proposalIdintegerID of the proposal whose outline changed.
data.statusstringOutline generation status. One of Generating, Completed, Failed.
data.outlinestringThe generated outline text, when status is Completed.
{
  "id": "evt_outline_def456",
  "type": "ProposalOutlineStatusChanged",
  "created": "2026-04-06T00:20:00Z",
  "data": {
    "proposalId": 4123,
    "status": "Completed",
    "outline": "1. Living Room AV System\n2. Master Bedroom Audio\n3. Outdoor Speakers"
  }
}

ProposalStatusChanged

Fired whenever a proposal’s status changes. Common transitions include Draft → Sent, Sent → Approved, and Approved → Ordered. The full proposal object is included in the payload. Event name: ProposalStatusChanged

Payload Fields

FieldTypeDescription
idintegerUnique identifier for the proposal.
createdDatestring (ISO 8601)When the proposal was created.
clientLastDecisionDatestring (ISO 8601)When the client last approved or declined.
lastCompletedDatestring (ISO 8601)When the proposal was last marked complete.
lastModifiedDatestring (ISO 8601)When the proposal was last modified by any actor.
lastModifiedByUserDatestring (ISO 8601)When the proposal was last modified by a user (not system).
financialSummaryobjectFinancial breakdown: partsSubtotal, partsTotal, laborTotal, feeTotal, proposalSubtotal, salesTax, and proposalTotal.
statusstringCurrent proposal status (e.g. Draft, Sent, Approved, Ordered).
{
  "id": 4123,
  "createdDate": "2026-04-05T18:00:00Z",
  "clientLastDecisionDate": "2026-04-06T00:22:00Z",
  "lastCompletedDate": null,
  "lastModifiedDate": "2026-04-06T00:22:19Z",
  "lastModifiedByUserDate": "2026-04-06T00:22:19Z",
  "financialSummary": {
    "partsSubtotal": 5200.00,
    "partsDiscountType": "Percentage",
    "partsDiscountPercentage": 0,
    "partsDiscount": 0,
    "partsTotal": 5200.00,
    "laborTotal": 800.00,
    "feeTotal": 0,
    "proposalSubtotal": 6000.00,
    "salesTax": {
      "taxStatus": "Taxable",
      "total": 450.00
    },
    "proposalTotal": 6450.00
  },
  "status": "Approved"
}

PersonModification

Fired when a contact record is created or updated in Portal.io. This Zapier-compatible event provides the full contact and primary location in a single payload. Event name: PersonModification

Payload Fields

FieldTypeDescription
idintegerContact ID.
partyTypestringIndividual or Company.
contactTypestringContact classification.
firstNamestringContact’s first name.
lastNamestringContact’s last name.
companyNamestringCompany name, when applicable.
contactEmailstringPrimary email address.
contactEmailCCstringCC email address.
contactPhonestringPrimary phone number.
locationobjectPrimary location object with id, street, suite, city, postalCode, state, stateAbbrev, country, phone.

PaymentStatusChange

Fired when a payment’s status changes in Portal.io (e.g. pending → collected, collected → refunded). Useful for triggering accounting system updates. Event name: PaymentStatusChange

OrderStatusChange

Fired when an order’s status changes in Portal.io (e.g. pending → submitted, submitted → received). Subscribe to this event to keep external fulfillment or purchasing systems in sync. Event name: OrderStatusChange