openapi: 3.1.0
info:
  title: MessagePipe Delivery Event Service
  version: "1.0"
  description: >
    Internal ingress for provider delivery webhooks (spec §C.8). Each provider's
    callbacks POST to a per-configuration URL; the request is authenticated by the
    provider's own webhook signature scheme, normalised to canonical delivery
    events, persisted, and fanned out to Kafka (`delivery.event`).
servers:
  - url: https://events.dev.messagepipe.dev
    description: Development
  - url: http://localhost:8081
    description: Local
tags:
  - { name: Inbound, description: Provider webhook ingestion (§C.8.2) }
  - { name: System, description: Health }
paths:
  /healthz:
    get:
      tags: [System]
      summary: Liveness / readiness (Postgres)
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Health" } } } }
        "503": { description: Database unavailable }
  /v1/inbound/{provider}/{configId}:
    post:
      tags: [Inbound]
      summary: Ingest a provider delivery webhook
      description: >
        Provider-specific webhook payload (opaque). The `provider` must match a
        registered adapter and the configuration's `provider_type`; the request is
        verified with the provider's webhook signature scheme using the stored
        config credentials. Body limit 2 MB.
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
            enum: [mock, sendgrid, mailgun, ses, resend, postmark, mailersend, zeptomail, mailtrap, elasticemail, sparkpost, smtp, twilio, termii, infobip, sns, telnyx, bandwidth, plivo, sinch, vonage, bird_sms, messagepipe, fcm, apns, onesignal, whatsapp, whatsapp_twilio, telegram, signal]
        - { name: configId, in: path, required: true, schema: { type: string }, description: provider_configurations id }
      requestBody:
        required: true
        description: Provider-specific webhook body (JSON or form, provider-dependent).
        content:
          application/json: { schema: { type: object, additionalProperties: true } }
      responses:
        "200":
          description: Parsed and processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  received: { type: integer, description: events parsed from the payload }
                  processed: { type: integer, description: events stored }
        "400": { description: Unreadable body, unparseable payload, or unsupported provider }
        "401": { description: Webhook signature verification failed }
        "404": { description: Unknown configuration, or provider mismatch }
        "500": { description: Internal error }
    get:
      tags: [Inbound]
      summary: Ingest a provider delivery webhook delivered by GET
      description: >
        For providers that notify with the event in the query string and sign the
        request target (Elastic Email's default delivery). Verified the same way as
        the POST. Every other provider answers 405.
      parameters:
        - { name: provider, in: path, required: true, schema: { type: string, enum: [elasticemail] } }
        - { name: configId, in: path, required: true, schema: { type: string }, description: provider_configurations id }
      responses:
        "200": { description: Parsed and processed }
        "400": { description: Unparseable query string }
        "401": { description: Webhook signature verification failed }
        "404": { description: Unknown configuration, or provider mismatch }
        "405":
          description: >
            Any other method (PUT, DELETE and the like on any provider; GET on every provider except
            Elastic Email). `Allow` lists what the provider accepts.
          headers: { Allow: { schema: { type: string, example: "GET, POST" }, description: "`GET, POST` for elasticemail, `POST` for every other provider" } }
  /v1/webhooks/managed/ses:
    post:
      tags: [Inbound]
      summary: Ingest managed email (MessagePipe's own SES account) delivery events
      description: >
        Managed email reports delivery events through one platform Amazon SNS topic instead of a
        per-configuration URL. The request must be an SNS message from exactly the topic in
        `MANAGED_SES_SNS_TOPIC_ARN`, with a valid SNS signature (certificate on an `sns.<region>.amazonaws.com`
        host). Each SES event is matched to its send by `provider_message_id`, which gives the workspace, and
        is then stored and published like any other delivery event. A `SubscriptionConfirmation` from the
        pinned topic with a valid signature is confirmed by a GET to its `SubscribeURL`, only when that URL is
        an https `sns.<region>.amazonaws.com` host. When `MANAGED_SES_SNS_TOPIC_ARN` is unset the endpoint
        answers 503 and processes nothing. Body limit 2 MB.
      requestBody:
        required: true
        description: An Amazon SNS HTTP(S) notification (JSON).
        content:
          application/json: { schema: { type: object, additionalProperties: true } }
      responses:
        "200":
          description: Processed (`received`, `processed`), or the subscription was confirmed (`confirmed`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  received: { type: integer }
                  processed: { type: integer }
                  confirmed: { type: boolean }
        "400": { description: Unreadable body, unparseable payload, or a SubscribeURL that is not an SNS host }
        "401": { description: Not from the pinned topic, or the SNS signature failed verification }
        "502": { description: SNS did not accept the subscription confirmation }
        "503": { description: Managed email webhook not configured, or an event could not be recorded and SNS should redeliver }
  /v1/webhooks/managed/sms:
    post:
      tags: [Inbound]
      summary: Ingest managed SMS (MessagePipe's own AWS End User Messaging account) delivery events
      description: >
        Managed SMS reports delivery events through one platform Amazon SNS topic (the event destination of
        the sending configuration set). Same rules as `/v1/webhooks/managed/ses`: the request must be an SNS
        message from exactly the topic in `MANAGED_SMS_SNS_TOPIC_ARN` with a valid SNS signature, each event
        is matched to its send by `provider_message_id` (the MessageId returned when the SMS was accepted)
        and stored and published like any other delivery event, and a signed `SubscriptionConfirmation` from
        the pinned topic is confirmed. `TEXT_DELIVERED` becomes `delivered`; a final failure (unreachable,
        invalid number, blocked, carrier-blocked, spam, expired) becomes `failed` with a fixed `code` and
        `reason`; pending and accepted-by-carrier events are not recorded again. When
        `MANAGED_SMS_SNS_TOPIC_ARN` is unset the endpoint answers 503 and processes nothing. Body limit 2 MB.
      requestBody:
        required: true
        description: An Amazon SNS HTTP(S) notification (JSON).
        content:
          application/json: { schema: { type: object, additionalProperties: true } }
      responses:
        "200":
          description: Processed (`received`, `processed`), or the subscription was confirmed (`confirmed`)
          content:
            application/json:
              schema:
                type: object
                properties:
                  received: { type: integer }
                  processed: { type: integer }
                  confirmed: { type: boolean }
        "400": { description: Unreadable body, unparseable payload, or a SubscribeURL that is not an SNS host }
        "401": { description: Not from the pinned topic, or the SNS signature failed verification }
        "502": { description: SNS did not accept the subscription confirmation }
        "503": { description: Managed SMS webhook not configured, or an event could not be recorded and SNS should redeliver }
components:
  schemas:
    Health:
      type: object
      properties: { status: { type: string, example: ok } }
