Wefunder Attribution Webhooks API

The Attribution Webhooks API from Wefunder — 5 operation(s) for attribution webhooks.

OpenAPI Specification

wefunder-attribution-webhooks-api-openapi.yml Raw ↑
openapi: 3.0.1
info:
  title: Wefunder API v2 Activity Attribution Webhooks API
  description: 'OAuth 2.0 API for accessing Wefunder data programmatically.


    ## Authentication


    This API uses OAuth 2.0 for authentication. To get started:


    1. Create an OAuth application at `/oauth/applications`

    2. Get your Client ID and Client Secret

    3. Implement the OAuth 2.0 Authorization Code flow

    4. Use the access token to make API requests


    ## Rate Limits


    - Standard API: 1,000 requests/hour per token

    - Admin API: 10,000 requests/hour per token


    ## Base URL


    All API requests should be made to: `https://api.wefunder.com/api/v2`

    '
  version: '2.0'
  contact:
    name: Wefunder API Support
    email: api@wefunder.com
servers:
- url: https://{environment}.wefunder.com/api/v2
  variables:
    environment:
      default: api
      enum:
      - api
      - staging
tags:
- name: Attribution Webhooks
paths:
  /campaigns/{campaign_id}/attribution/webhooks:
    get:
      tags:
      - Attribution Webhooks
      summary: List attribution webhook subscriptions
      description: 'Returns all active webhook subscriptions for this campaign belonging to

        your OAuth application.


        **Tier 1** endpoint requiring admin-approved access with the

        `read:attribution:anonymized` scope.

        '
      operationId: listWebhookSubscriptions
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionListEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      tags:
      - Attribution Webhooks
      summary: Create webhook subscription
      description: 'Creates a new webhook subscription for this campaign. When attributed

        investments change state, we''ll POST a JSON payload to your target URL.


        **Tier 1** endpoint requiring admin-approved access.


        ## Webhook Payload


        Payloads include anonymized data (tokens, amount tiers) matching the

        `/attribution/investments` endpoint.


        ## Security


        - `target_url` must use HTTPS

        - `target_url` cannot resolve to private/internal IPs (SSRF protection)

        - Payloads are signed with HMAC-SHA256 using the returned `secret`


        ## Verification


        Verify webhook signatures using the `X-Wefunder-Signature` header:

        ```

        timestamp = headers[''X-Wefunder-Timestamp'']

        signature = headers[''X-Wefunder-Signature'']

        expected = "sha256=" + HMAC-SHA256(secret, timestamp + "." + raw_body)

        ```


        Reject requests where:

        - Signature doesn''t match

        - Timestamp is more than 5 minutes old (replay protection)

        '
      operationId: createWebhookSubscription
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - target_url
              - events
              properties:
                target_url:
                  type: string
                  format: uri
                  description: HTTPS URL to receive webhook POSTs
                  example: https://yourapp.com/webhooks/wefunder
                events:
                  type: array
                  description: Events to subscribe to
                  items:
                    type: string
                    enum:
                    - investment.applied
                    - investment.confirmed
                    - investment.canceled
                  example:
                  - investment.applied
                  - investment.confirmed
      responses:
        '201':
          description: Subscription created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionWithSecretEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: Validation error (invalid URL, events, or limit reached)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}:
    get:
      tags:
      - Attribution Webhooks
      summary: Get webhook subscription details
      description: 'Returns details for a specific webhook subscription including recent

        delivery history.

        '
      operationId: getWebhookSubscription
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      - $ref: '#/components/parameters/webhook_id'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionWithRecentDeliveriesEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      tags:
      - Attribution Webhooks
      summary: Delete webhook subscription
      description: 'Deactivates a webhook subscription. The subscription will stop receiving

        events but historical delivery data is retained for 7 days.

        '
      operationId: deleteWebhookSubscription
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      - $ref: '#/components/parameters/webhook_id'
      responses:
        '204':
          description: Subscription deleted
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/test:
    post:
      tags:
      - Attribution Webhooks
      summary: Send test webhook
      description: 'Sends a test webhook to verify your endpoint is working correctly.

        The test payload includes a `"event": "test"` field to distinguish

        it from real events.

        '
      operationId: testWebhookSubscription
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      - $ref: '#/components/parameters/webhook_id'
      responses:
        '200':
          description: Test webhook queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookTestResultEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Subscription is inactive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/regenerate_secret:
    post:
      tags:
      - Attribution Webhooks
      summary: Regenerate webhook signing secret
      description: 'Generates a new HMAC signing secret for the webhook subscription.

        The old secret is immediately invalidated.


        **Important**: Store the new secret securely - it cannot be retrieved later.

        '
      operationId: regenerateWebhookSecret
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      - $ref: '#/components/parameters/webhook_id'
      responses:
        '200':
          description: Secret regenerated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionWithSecret2Envelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /campaigns/{campaign_id}/attribution/webhooks/{webhook_id}/reactivate:
    post:
      tags:
      - Attribution Webhooks
      summary: Reactivate disabled webhook
      description: 'Reactivates a webhook subscription that was auto-disabled after

        consecutive failures. Resets the failure counter to 0.


        Use this after fixing issues with your webhook endpoint.

        '
      operationId: reactivateWebhookSubscription
      security:
      - bearerAuth:
        - read:attribution:anonymized
      parameters:
      - $ref: '#/components/parameters/campaign_id'
      - $ref: '#/components/parameters/webhook_id'
      responses:
        '200':
          description: Webhook reactivated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscriptionEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Subscription not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: Webhook is already active
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    WebhookDelivery:
      type: object
      description: A single webhook delivery attempt
      properties:
        id:
          type: integer
          example: 456
        delivery_uuid:
          type: string
          format: uuid
          description: Unique delivery identifier (use for idempotency)
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        event_type:
          type: string
          description: The event that triggered this delivery
          example: investment.applied
        status:
          type: string
          enum:
          - pending
          - delivered
          - failed
          example: delivered
        attempts:
          type: integer
          description: Number of delivery attempts
          example: 1
        response_code:
          type: integer
          nullable: true
          description: HTTP status code from the last attempt
          example: 200
        last_attempt_at:
          type: string
          format: date-time
          nullable: true
          example: '2025-03-15T10:31:00Z'
        created_at:
          type: string
          format: date-time
          example: '2025-03-15T10:30:00Z'
    WebhookSubscriptionWithRecentDeliveriesEnvelope:
      type: object
      properties:
        data:
          allOf:
          - $ref: '#/components/schemas/WebhookSubscription'
          - type: object
            properties:
              recent_deliveries:
                type: array
                description: Last 10 delivery attempts
                items:
                  $ref: '#/components/schemas/WebhookDelivery'
    WebhookSubscriptionWithSecret2Envelope:
      type: object
      properties:
        data:
          allOf:
          - $ref: '#/components/schemas/WebhookSubscription'
          - type: object
            properties:
              secret:
                type: string
                description: New HMAC signing secret
                example: whsec_a1b2c3d4e5f6g7h8i9j0...
    WebhookTestResultEnvelope:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/WebhookTestResult'
    WebhookSubscriptionListEnvelope:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookSubscription'
        meta:
          type: object
          properties:
            total:
              type: integer
              description: Number of active subscriptions
              example: 2
            limit:
              type: integer
              description: Maximum subscriptions per application
              example: 10
    WebhookSubscriptionEnvelope:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/WebhookSubscription'
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              example: unauthorized
            message:
              type: string
              example: Invalid or expired token
            details:
              type: object
              additionalProperties: true
            request_id:
              type: string
              description: Unique identifier for this request. Quote it in support tickets.
              example: req_abc123
            remediation:
              type: string
              description: When present, a hint on how to resolve the error.
              example: Obtain a new access token using the OAuth 2.0 flow.
    WebhookSubscriptionWithSecretEnvelope:
      type: object
      properties:
        data:
          allOf:
          - $ref: '#/components/schemas/WebhookSubscription'
          - type: object
            properties:
              secret:
                type: string
                description: 'HMAC signing secret. **Only returned on create.**

                  Store this securely - it cannot be retrieved later.

                  '
                example: whsec_a1b2c3d4e5f6g7h8i9j0...
    WebhookTestResult:
      type: object
      properties:
        message:
          type: string
          example: Test webhook queued for delivery
        delivery_id:
          type: string
          format: uuid
          example: f47ac10b-58cc-4372-a567-0e02b2c3d479
        payload_preview:
          type: object
          description: The payload that was sent
    WebhookSubscription:
      type: object
      description: A webhook subscription for attribution events
      properties:
        id:
          type: integer
          example: 123
        campaign_id:
          type: integer
          example: 789
        target_url:
          type: string
          format: uri
          example: https://yourapp.com/webhooks/wefunder
        events:
          type: array
          items:
            type: string
            enum:
            - investment.applied
            - investment.confirmed
            - investment.canceled
          example:
          - investment.applied
          - investment.confirmed
        active:
          type: boolean
          description: Whether the subscription is active
          example: true
        consecutive_failures:
          type: integer
          description: Number of consecutive delivery failures (resets on success)
          example: 0
        created_at:
          type: string
          format: date-time
          example: '2025-03-15T10:30:00Z'
        updated_at:
          type: string
          format: date-time
          example: '2025-03-15T10:30:00Z'
  parameters:
    campaign_id:
      name: campaign_id
      in: path
      required: true
      description: The fundraise/campaign ID
      schema:
        type: integer
      example: 789
    webhook_id:
      name: webhook_id
      in: path
      required: true
      description: The webhook subscription ID
      schema:
        type: integer
      example: 123
  responses:
    Forbidden:
      description: 'The authenticated user does not have permission to access this resource.

        This typically means:

        - The user is authenticated but lacks the required OAuth scope

        - The resource belongs to a different user

        - The user''s role doesn''t allow this operation


        Check that your OAuth token includes the necessary scopes for this endpoint.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: 'Authentication required or token is invalid/expired. This error occurs when:

        - No Authorization header is provided

        - The access token is invalid or malformed

        - The access token has expired

        - The access token has been revoked


        To resolve: Obtain a new access token using the OAuth 2.0 flow.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    bearerAuth:
      type: oauth2
      description: OAuth 2.0 authorization with access tokens
      flows:
        authorizationCode:
          authorizationUrl: https://wefunder.com/oauth/authorize
          tokenUrl: https://wefunder.com/oauth/token
          scopes:
            read:public: Read public deal data (explore offerings); requires no user context
            read:profile: Read user profile information
            read:investments: Read user investment data
            read:campaigns: Read campaign information for companies you founded
            read:attribution:aggregate: Read aggregate attribution statistics (Tier 0)
            read:attribution:anonymized: Read anonymized attribution data (Tier 1 - requires approval)
            read:attribution:full: Read full attribution data with investor PII (Tier 2 - founders only)
            admin:attribution: Administrative access to attribution system (Tier 3 - Wefunder admins only)
            read:syndicates: View syndicates, members, deals, and portfolio
            write:syndicates: Manage members, update settings, operate on deals
            read:spvs: View partner SPVs, their invites, sessions, and investments
            write:spvs: Create and manage partner SPVs, invites, and investment sessions
            read:webhooks: View webhook subscriptions
            write:webhooks: Create, update, and delete webhook subscriptions
        clientCredentials:
          tokenUrl: https://wefunder.com/oauth/token
          scopes:
            read:public: Read public deal data. The only scope a server-side (client_credentials) key may hold — it acts as the app, with no user, so it can never read user-scoped data.