> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a Webhook Subscription

> POST /public/webhook/subscribe — Registers a new webhook endpoint for one or more event types. Returns the subscription ID and signing secret key.

Registers a new webhook subscription for your dealer account. Provide an HTTPS callback URL and the list of event types you want to receive. Portal.io will POST a signed JSON payload to your URL whenever a subscribed event occurs. The response includes a `secretKey` — store this securely, as it is used to verify the authenticity of every webhook delivery sent to your endpoint.

<Warning>
  The `secretKey` is only returned once at creation time. If you lose it, you will need to update the subscription to generate a new one.
</Warning>

<RequestExample>
  ```bash curl theme={null}
  curl -i -X POST \
    'https://api.portal.io/public/webhook/subscribe' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -H 'X-MSS-API-APPID: YOUR_APP_ID' \
    -H 'X-MSS-API-USERKEY: YOUR_USER_KEY' \
    -H 'X-MSS-CUSTOM-DATE: Mon, 06 Apr 2026 00:22:19 GMT' \
    -H 'X-MSS-SIGNATURE: BASE64_SIGNATURE' \
    -d 'Url=https%3A%2F%2Fapp.example.com%2Fwebhooks%2Fportal' \
    -d 'Events=proposal.status_changed' \
    -d 'Events=proposal.build.status_update' \
    -d 'Description=Production+webhook'
  ```

  ```python python theme={null}
  import requests
  from portal_auth import sign_request

  url = "https://api.portal.io/public/webhook/subscribe"
  timestamp = "Mon, 06 Apr 2026 00:22:19 GMT"
  content_type = "application/x-www-form-urlencoded"
  signature = sign_request("POST", url, content_type, timestamp, "YOUR_USER_KEY", "YOUR_SECRET_KEY")

  response = requests.post(url, headers={
      "Accept": "application/json",
      "Content-Type": content_type,
      "X-MSS-API-APPID": "YOUR_APP_ID",
      "X-MSS-API-USERKEY": "YOUR_USER_KEY",
      "X-MSS-CUSTOM-DATE": timestamp,
      "X-MSS-SIGNATURE": signature
  }, data=[
      ("Url", "https://app.example.com/webhooks/portal"),
      ("Events", "proposal.status_changed"),
      ("Events", "proposal.build.status_update"),
      ("Description", "Production webhook"),
  ])
  print(response.json())
  ```

  ```javascript node.js theme={null}
  const { signRequest } = require('./portalAuth');

  const url = "https://api.portal.io/public/webhook/subscribe";
  const timestamp = new Date().toUTCString();
  const contentType = "application/x-www-form-urlencoded";
  const signature = signRequest("POST", url, contentType, timestamp, "YOUR_USER_KEY", "YOUR_SECRET_KEY");

  const response = await fetch(url, {
      method: "POST",
      headers: {
          "Accept": "application/json",
          "Content-Type": contentType,
          "X-MSS-API-APPID": "YOUR_APP_ID",
          "X-MSS-API-USERKEY": "YOUR_USER_KEY",
          "X-MSS-CUSTOM-DATE": timestamp,
          "X-MSS-SIGNATURE": signature
      },
      body: new URLSearchParams([
          ["Url", "https://app.example.com/webhooks/portal"],
          ["Events", "proposal.status_changed"],
          ["Events", "proposal.build.status_update"],
          ["Description", "Production webhook"],
      ])
  });
  const data = await response.json();
  console.log(data);
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "subscriptionId": 42,
    "url": "https://app.example.com/webhooks/portal",
    "description": "Production webhook",
    "enabled": true,
    "secretKey": "whsec_a1b2c3d4e5f6...",
    "events": [
      "proposal.status_changed",
      "proposal.build.status_update"
    ]
  }
  ```
</ResponseExample>


## OpenAPI

````yaml POST /public/webhook/subscribe
openapi: 3.1.0
info:
  title: Portal.Api
  version: '1.0'
servers:
  - url: http://127.0.0.1:5000
security: []
tags: []
paths:
  /public/webhook/subscribe:
    post:
      tags:
        - Webhooks
      summary: Create Webhook Subscription
      description: >-
        Creates a new webhook subscription for the authenticated dealer. The
        response includes the signing secret key used to verify delivered
        webhook requests.
      operationId: SubscribeToWebhook
      parameters:
        - $ref: '#/components/parameters/Accept'
        - name: X-MSS-API-APPID
          in: header
          description: Application Id
          required: true
          schema:
            type: string
        - name: X-MSS-CUSTOM-DATE
          in: header
          description: A date timestamp of the request
          required: true
          schema:
            type: string
        - name: X-MSS-SIGNATURE
          in: header
          description: A signature for the request
          required: true
          schema:
            type: string
        - name: X-MSS-API-USERKEY
          in: header
          description: User API Key
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                Url:
                  description: >-
                    Absolute HTTPS URL that will receive webhook POST payloads.
                    Must use the https:// scheme and resolve to a publicly
                    routable host — private, loopback, and link-local addresses
                    are rejected. The value is trimmed before storage.
                  type: string
                Description:
                  description: >-
                    Optional human-readable label for this subscription. Trimmed
                    before storage; whitespace-only values are stored as null.
                  type: string
                Events:
                  description: >-
                    One or more event-type names to subscribe to. Duplicate
                    names are ignored case-insensitively. Returns 400 listing
                    any unrecognised event names. Currently supported webhook
                    event names are implementation-defined; see the published
                    webhook documentation for the current set.
                  type: array
                  items:
                    type: string
              required:
                - Url
                - Events
        required: true
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionResponse'
        '400':
          description: >-
            Validation failed. Common causes: subscription not found or does not
            belong to this account, URL is not HTTPS or resolves to a private
            address, unrecognised event names, or no changes were provided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '401':
          description: >-
            HMAC signature validation failed or credentials are invalid. Verify
            X-MSS-SIGNATURE, X-MSS-CUSTOM-DATE, and X-MSS-API-USERKEY headers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '402':
          description: >-
            The dealer's subscription is inactive or expired. An active
            subscription is required to use this endpoint.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '403':
          description: You do not have permission for this action.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
      deprecated: false
      x-codeSamples: []
components:
  parameters:
    Accept:
      name: Accept
      in: header
      description: Accept Header
      required: true
      schema:
        type: string
        enum:
          - application/json
  schemas:
    WebhookSubscriptionResponse:
      title: WebhookSubscriptionResponse
      required:
        - subscriptionId
        - url
        - enabled
        - events
      properties:
        subscriptionId:
          description: Unique numeric identifier of the webhook subscription
          type: integer
          format: int64
        url:
          description: HTTPS URL that receives webhook POST deliveries
          type: string
        description:
          description: Optional human-readable label for the subscription
          type: string
        enabled:
          description: Whether the subscription is enabled
          type: boolean
        secretKey:
          description: >-
            Signing secret used to verify webhook deliveries. Only returned when
            a new secret is issued, such as during create or update operations
          type: string
        events:
          description: Event names that trigger this subscription
          type: array
          items:
            type: string
      description: Webhook subscription returned by the public webhook management API
      type: object
    HttpError:
      title: HttpError
      properties:
        errorCode:
          type: string
        stackTrace:
          type: string
        contentType:
          type: string
        headers:
          $ref: '#/components/schemas/Dictionary_String_String_'
        cookies:
          type: array
          items:
            $ref: '#/components/schemas/Cookie'
        status:
          type: integer
          format: int32
        statusCode:
          type: string
        statusDescription:
          type: string
        response:
          $ref: '#/components/schemas/Object'
        responseFilter:
          $ref: '#/components/schemas/IContentTypeWriter'
        requestContext:
          $ref: '#/components/schemas/IRequest'
        paddingLength:
          type: integer
          format: int32
        resultScope:
          $ref: '#/components/schemas/Func_IDisposable_'
        options:
          $ref: '#/components/schemas/IDictionary_String_String_'
        responseStatus:
          $ref: '#/components/schemas/ResponseStatus'
        targetSite:
          $ref: '#/components/schemas/MethodBase'
        message:
          type: string
        data:
          $ref: '#/components/schemas/IDictionary'
        innerException:
          $ref: '#/components/schemas/Exception'
        helpLink:
          type: string
        source:
          type: string
        hResult:
          type: integer
          format: int32
      description: HttpError
      type: object
    Dictionary_String_String_:
      title: Dictionary<String,String>
      additionalProperties:
        type: string
      description: Dictionary<String,String>
      type: object
    Cookie:
      title: Cookie
      properties: {}
      description: Cookie
      type: object
    Object:
      properties: {}
      description: Object
      type: object
    IContentTypeWriter:
      title: IContentTypeWriter
      properties: {}
      description: IContentTypeWriter
      type: object
    IRequest:
      title: IRequest
      properties: {}
      description: IRequest
      type: object
    Func_IDisposable_:
      title: Func`1
      properties: {}
      description: Func<IDisposable>
      type: object
    IDictionary_String_String_:
      title: IDictionary<String,String>
      additionalProperties:
        type: string
      description: IDictionary<String,String>
      type: object
    ResponseStatus:
      title: ResponseStatus
      properties:
        errorCode:
          type: string
        message:
          type: string
        stackTrace:
          type: string
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ResponseError'
        meta:
          $ref: '#/components/schemas/Dictionary_String_String_'
      description: ResponseStatus
      type: object
    MethodBase:
      title: MethodBase
      properties: {}
      description: MethodBase
      type: object
    IDictionary:
      title: IDictionary
      properties: {}
      description: IDictionary
      type: object
    Exception:
      title: Exception
      properties: {}
      description: Exception
      type: object
    ResponseError:
      title: ResponseError
      properties:
        errorCode:
          type: string
        fieldName:
          type: string
        message:
          type: string
        meta:
          $ref: '#/components/schemas/Dictionary_String_String_'
      description: ResponseError
      type: object

````