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

# Receive a provider webhook on a tenant connection

> Accepts a settlement event pushed by the payment provider on a per-tenant webhook connection, persists it verbatim and returns 202 only after that persistence succeeds. This route is served only by a multi-tenant deployment; a single-tenant deployment serves the unparameterised provider webhook route instead, and this document advertises both because it is generated without a deployment mode. The connection_token path segment names a webhook connection this service issued when a tenant connected the provider: it selects the tenant the delivery belongs to, the storage it is written to, and the key its signature is verified under. Authentication is an HMAC-SHA256 signature over the raw request body, sent as lowercase hex in x-webhook-baas-signature and computed with the secret agreed for that one connection, not a platform identity and not a key shared between tenants. A connection_token that names no connection, a connection that has been revoked, and a signature that does not match the body are refused with the same 401 and cannot be told apart. If the tenant's storage is unavailable the event is not persisted and the response is 503 (retryable) instead of 202. The body is the provider envelope: pactualId, entity and status are required, clientRequestId and createTimestamp are optional, and any further member is accepted and stored as sent. Member names are matched without regard to case. The x-webhook-baas-entity and x-webhook-baas-event headers are optional copies of entity and status; where a header disagrees with the body the body is authoritative, because only the body is signed. The x-webhook-baas-notifyid header identifies the logical notification and, when sent, is what this endpoint deduplicates on; a delivery without it is still accepted and deduplicated on a key derived from pactualId, entity, status and createTimestamp. The x-webhook-baas-deliveryid header names one attempt to deliver that notification rather than the notification itself; it is optional, it decides nothing, and it is recorded only in this endpoint's logs, so that a delivery can be matched against the provider's own list of failed attempts. A delivery whose status names a provider TRANSPORT event rather than a business one — PING, which the provider's own webhook test emits and which belongs to no entity's event catalogue — is the single exception to all of the above: it is answered 202 without being deduplicated and without being persisted.



## OpenAPI

````yaml /en/openapi/v3-current/payments.yaml post /v1/webhooks/btg/{connection_token}
openapi: 3.1.0
info:
  description: >-
    API for the Lerian Payments interface via BTG. It covers boleto issuance,
    cancellation, installments, and queries; bill payments (bankslip, utilities,
    and DARF), cancellation, and queries; aggregated boleto and payment
    dashboards; provider connection and outbound webhook configuration; and the
    provider webhook receiver for settlement events.


    ERROR BODIES. A failed request arrives in one of two shapes, and which one
    you get depends on where the service catches the failure. A failure that the
    request pipeline catches before the API layer sees it answers a flat JSON
    body on application/json; the idempotency check is the pipeline rule you
    meet most often. Authentication and authorization refusals follow the
    response contract documented by the operation. Everything the API layer
    catches answers an RFC 9457 problem document on application/problem+json.
    Read the media type to tell the two apart. The code member holds the same
    PBP-NNNN value in both, so branch on it. One pipeline refusal is not listed
    on the operations below: while a first request under the same idempotency
    key is still in flight, a retry is answered 409 with code PBP-0007 and the
    flat body.
  title: Payments — via BTG API
  version: 1.0.0
servers:
  - url: https://payments.sandbox.lerian.net
security: []
tags:
  - description: >-
      Tenant-level setup: connecting the banking provider and configuring where
      status notifications are delivered.
    name: Admin
  - description: >-
      Issuing, querying and cancelling boletos, including installment series and
      the rendered PDF.
    name: Boletos
  - description: >-
      Aggregated counts, amounts and breakdowns over a period, for boletos and
      for payments.
    name: Dashboards
  - description: >-
      Paying bankslips, utility bills and DARF tax slips, and querying or
      cancelling those payments.
    name: Payments
  - description: >-
      The webhook the payment provider posts settlement events to. Authenticated
      with a credential agreed with the provider, not with the platform identity
      used by the rest of this API.
    name: Settlement
paths:
  /v1/webhooks/btg/{connection_token}:
    post:
      tags:
        - Settlement
      summary: Receive a provider webhook on a tenant connection
      description: >-
        Accepts a settlement event pushed by the payment provider on a
        per-tenant webhook connection, persists it verbatim and returns 202 only
        after that persistence succeeds. This route is served only by a
        multi-tenant deployment; a single-tenant deployment serves the
        unparameterised provider webhook route instead, and this document
        advertises both because it is generated without a deployment mode. The
        connection_token path segment names a webhook connection this service
        issued when a tenant connected the provider: it selects the tenant the
        delivery belongs to, the storage it is written to, and the key its
        signature is verified under. Authentication is an HMAC-SHA256 signature
        over the raw request body, sent as lowercase hex in
        x-webhook-baas-signature and computed with the secret agreed for that
        one connection, not a platform identity and not a key shared between
        tenants. A connection_token that names no connection, a connection that
        has been revoked, and a signature that does not match the body are
        refused with the same 401 and cannot be told apart. If the tenant's
        storage is unavailable the event is not persisted and the response is
        503 (retryable) instead of 202. The body is the provider envelope:
        pactualId, entity and status are required, clientRequestId and
        createTimestamp are optional, and any further member is accepted and
        stored as sent. Member names are matched without regard to case. The
        x-webhook-baas-entity and x-webhook-baas-event headers are optional
        copies of entity and status; where a header disagrees with the body the
        body is authoritative, because only the body is signed. The
        x-webhook-baas-notifyid header identifies the logical notification and,
        when sent, is what this endpoint deduplicates on; a delivery without it
        is still accepted and deduplicated on a key derived from pactualId,
        entity, status and createTimestamp. The x-webhook-baas-deliveryid header
        names one attempt to deliver that notification rather than the
        notification itself; it is optional, it decides nothing, and it is
        recorded only in this endpoint's logs, so that a delivery can be matched
        against the provider's own list of failed attempts. A delivery whose
        status names a provider TRANSPORT event rather than a business one —
        PING, which the provider's own webhook test emits and which belongs to
        no entity's event catalogue — is the single exception to all of the
        above: it is answered 202 without being deduplicated and without being
        persisted.
      operationId: receiveProviderWebhookByConnection
      parameters:
        - description: >-
            HMAC-SHA256 of the raw request body, lowercase hex. WHICH key signs
            it depends on the route: the unparameterised route verifies against
            the deployment-wide provider secret, while the per-connection route
            verifies against the signing key issued for that connection_token
            and nothing else — a delivery signed with the deployment-wide secret
            is refused there. It is verified by a per-operation middleware that
            runs before any schema check, so a request that omits it, or carries
            a value that does not match the body, is refused with 401 PBP-0500
            and never with a schema error.
          in: header
          name: x-webhook-baas-signature
          required: true
          schema:
            description: >-
              HMAC-SHA256 of the raw request body, lowercase hex. WHICH key
              signs it depends on the route: the unparameterised route verifies
              against the deployment-wide provider secret, while the
              per-connection route verifies against the signing key issued for
              that connection_token and nothing else — a delivery signed with
              the deployment-wide secret is refused there. It is verified by a
              per-operation middleware that runs before any schema check, so a
              request that omits it, or carries a value that does not match the
              body, is refused with 401 PBP-0500 and never with a schema error.
            type: string
        - description: >-
            Provider entity name, repeating the body's entity member.
            Cross-checked against it; the body decides, because only the body is
            under the signature.
          in: header
          name: x-webhook-baas-entity
          schema:
            description: >-
              Provider entity name, repeating the body's entity member.
              Cross-checked against it; the body decides, because only the body
              is under the signature.
            type: string
        - description: >-
            Provider event name, which is the same value as the body's status
            member. Cross-checked against it; the body decides.
          in: header
          name: x-webhook-baas-event
          schema:
            description: >-
              Provider event name, which is the same value as the body's status
              member. Cross-checked against it; the body decides.
            type: string
        - description: >-
            Provider identifier of the LOGICAL notification, as opposed to one
            delivery attempt of it. When present it becomes this delivery's
            deduplication key; when absent the key is derived from the envelope
            instead, and the delivery is still accepted.
          in: header
          name: x-webhook-baas-notifyid
          schema:
            description: >-
              Provider identifier of the LOGICAL notification, as opposed to one
              delivery attempt of it. When present it becomes this delivery's
              deduplication key; when absent the key is derived from the
              envelope instead, and the delivery is still accepted.
            type: string
        - description: >-
            Provider identifier of ONE ATTEMPT to deliver a notification, as
            opposed to the notification itself. It decides nothing: it is never
            deduplicated on and no column holds it, because an attempt
            identifier is fresh on every retry. It is recorded in this
            endpoint's logs so a delivery can be matched against the provider's
            own list of failed attempts. A delivery without it is still
            accepted.
          in: header
          name: x-webhook-baas-deliveryid
          schema:
            description: >-
              Provider identifier of ONE ATTEMPT to deliver a notification, as
              opposed to the notification itself. It decides nothing: it is
              never deduplicated on and no column holds it, because an attempt
              identifier is fresh on every retry. It is recorded in this
              endpoint's logs so a delivery can be matched against the
              provider's own list of failed attempts. A delivery without it is
              still accepted.
            type: string
        - description: >-
            Optional provider correlation identifier. Persisted with the event
            when present.
          in: header
          name: X-Correlation-Id
          schema:
            description: >-
              Optional provider correlation identifier. Persisted with the event
              when present.
            type: string
        - description: >-
            Opaque token identifying the webhook connection this service issued
            for one tenant and one provider. It selects the tenant the delivery
            belongs to, the storage it is written to, and the secret its body
            signature is verified under. A token that names no connection and a
            token whose connection has been revoked are refused identically, and
            neither is echoed back.
          in: path
          name: connection_token
          required: true
          schema:
            description: >-
              Opaque token identifying the webhook connection this service
              issued for one tenant and one provider. It selects the tenant the
              delivery belongs to, the storage it is written to, and the secret
              its body signature is verified under. A token that names no
              connection and a token whose connection has been revoked are
              refused identically, and neither is echoed back.
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProviderWebhookPayload'
        required: true
      responses:
        '202':
          description: Accepted
        '400':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Bad Request
        '401':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Unauthorized
        '413':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Request Entity Too Large
        '415':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Unsupported Media Type
        '422':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Unprocessable Entity
        '500':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Internal Server Error
        '503':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Detail'
          description: Service Unavailable
components:
  schemas:
    ProviderWebhookPayload:
      additionalProperties: true
      properties:
        clientRequestId:
          description: >-
            Identifier this service sent when it created the entity, echoed back
            for correlation. Optional here even though the provider envelope
            declares it required.
          type: string
        createTimestamp:
          description: >-
            Instant the provider recorded for this state, RFC 3339. Optional
            here: a delivery without one means no reference instant, not a
            malformed request.
          format: date-time
          type: string
        entity:
          description: >-
            Provider entity name the event belongs to, for example
            PaymentSlipPay or TaxDarfPayment. Cross-checked against
            x-webhook-baas-entity; the body wins.
          type: string
        pactualId:
          description: >-
            Provider-assigned primary key of the entity this event is about. A
            string representation of a GUID in every delivery seen so far, but
            not validated as one: the field is the provider's to shape.
          type: string
        status:
          description: >-
            Provider state the entity moved to, for example PAID. This IS the
            provider's event name — the x-webhook-baas-event header carries the
            same value — and it is stored as sent.
          type: string
      required:
        - pactualId
        - entity
        - status
      type: object
    Detail:
      additionalProperties: true
      properties:
        code:
          description: >-
            Stable, machine-readable domain error code scoped to the emitting
            service (format: <SERVICE>-NNNN).
          type: string
        detail:
          description: >-
            A human-readable explanation specific to this occurrence of the
            problem.
          examples:
            - Property foo is required but is missing.
          type: string
        errors:
          description: Optional list of individual error details
          items:
            $ref: '#/components/schemas/ErrorDetail'
          type:
            - array
            - 'null'
        instance:
          description: >-
            A URI reference that identifies the specific occurrence of the
            problem.
          examples:
            - https://example.com/error-log/abc123
          format: uri
          type: string
        status:
          description: HTTP status code
          examples:
            - 400
          format: int64
          type: integer
        title:
          description: >-
            A short, human-readable summary of the problem type. This value
            should not change between occurrences of the error.
          examples:
            - Bad Request
          type: string
        type:
          default: about:blank
          description: A URI reference to human-readable documentation for the error.
          examples:
            - https://example.com/errors/example
          format: uri
          type: string
        upstream:
          $ref: '#/components/schemas/Upstream'
          description: >-
            RFC 9457 extension member: the error a proxied third-party provider
            reported. Absent unless the emitting service explicitly surfaced
            one.
      type: object
    ErrorDetail:
      additionalProperties: false
      properties:
        location:
          description: >-
            Where the error occurred, e.g. 'body.items[3].tags' or
            'path.thing-id'
          type: string
        message:
          description: Error message text
          type: string
        value:
          description: The value at the given location
      type: object
    Upstream:
      additionalProperties: false
      properties:
        code:
          description: The upstream provider's own error code, verbatim.
          examples:
            - E4001
          type: string
        message:
          description: >-
            The upstream provider's own error message, verbatim (bounded, never
            its raw response body).
          examples:
            - account not found at provider
          type: string
      type: object

````