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

# Replay a past event

> POST /events/{event_id}/replay — encola una nueva entrega webhook para un evento existente.

Encola una nueva entrega webhook contra tu URL **actual** con el secreto **actual**. El `X-ZoPay-Event-Id` será el mismo `evt_…` que la entrega original — tu handler debe deduplicar por ese id.

* **Idempotencia obligatoria.** Misma `(org, Idempotency-Key, body)` dentro de 24h devuelve la entrega original sin encolar otra.
* **Replayable siempre.** Incluso eventos que ya entregaron 2xx pueden replicarse.
* **Auditoría acumulativa.** Cada replay crea una fila nueva en `webhook_deliveries` — el historial crece, no muta.

| Trigger                               | HTTP | Code                              |
| ------------------------------------- | ---- | --------------------------------- |
| Evento no existe (o cross-tenant)     | 404  | `event_not_found`                 |
| Ningún webhook endpoint habilitado    | 400  | `webhook_endpoint_not_configured` |
| Mismo `Idempotency-Key` con otro body | 409  | `idempotency_conflict`            |

Concepto en [Events](/api-reference/events).


## OpenAPI

````yaml POST /events/{event_id}/replay
openapi: 3.1.0
info:
  title: ZoPay Connect API
  description: >-
    B2B partner API for crypto custody, deposits, and payouts. Authenticate
    every request with a Bearer token (``sk_live_…`` for production,
    ``sk_test_…`` for sandbox).
  version: v1
servers:
  - url: https://api.zopay.cash/connect/v1
    description: Production
  - url: https://dev.api.zopay.cash/connect/v1
    description: Development
security:
  - BearerAuth: []
tags:
  - name: capabilities
    description: >-
      Static product catalog: which assets, networks, and fiat valuations this
      tenant can use. Poll on app startup to drive every UI surface (network
      pickers, fee warnings, fiat selection).
  - name: addresses
    description: >-
      Per-user deposit addresses. Mint with ``external_ref`` to attribute to one
      of your users; omit for a tenant-treasury (pooled) address.
  - name: payment-intents
    description: >-
      Single-use payment requests. Mint an intent for a specific amount +
      currency set; we expand it to per-network deposit addresses. Pass
      ``external_ref`` to attribute the intent to one of your users for
      downstream webhook reconciliation.
  - name: balances
    description: >-
      Per-user or tenant-wide balance snapshot with multi-fiat valuation (USD,
      PEN, EUR, MXN, ARS, COP, DOP).
  - name: transactions
    description: >-
      Unified ledger covering deposits, payouts, internal transfers, and (after
      Phase 4) conversions and ramps.
  - name: payouts
    description: >-
      Outbound money movement: get a signed quote, then execute it. Quote IDs
      are single-use and expire in 60s.
  - name: conversions
    description: '[Coming soon] Crypto-to-crypto conversions.'
  - name: ramps
    description: '[Coming soon] Fiat <-> crypto hosted-checkout sessions.'
  - name: webhooks
    description: '[Coming soon] Subscribe to event streams + replay past events.'
  - name: health
    description: >-
      Auth + connectivity probe. Use to validate your API key works before
      depending on a priced endpoint.
  - name: test-helpers
    description: >-
      Sandbox-only simulators. Let you exercise webhook handlers and downstream
      state transitions without real testnet activity. Routes return 404 to
      ``sk_live_…`` keys.
paths:
  /events/{event_id}/replay:
    post:
      tags:
        - events
      summary: Replay a past event
      description: >-
        Re-enqueue a webhook delivery for an existing event. The delivery is
        queued for async fan-out to your CURRENT webhook endpoint URL with the
        current signing secret -- this is the right escape hatch for ``my server
        was down`` or ``I rotated my signing secret and need to verify``.


        Every replay creates a NEW ``webhook_deliveries`` row with a fresh
        ``attempt_count`` and ``max_attempts`` budget. The original delivery row
        (if any) is untouched -- the audit trail accumulates rather than
        mutating. A partner who replays the same event ten times sees ten
        delivery rows.


        Replayable always: even events that were originally delivered with a 2xx
        can be replayed (partner backups, dev-env debugging). Receivers should
        dedupe on the ``X-ZoPay-Event-Id`` header -- the same ``evt_...`` id
        appears on every retry + replay of the same event.


        Idempotency: the ``Idempotency-Key`` header is required (per Connect's
        mutating-POST contract). Same (org, key, body) within 24h replays the
        stored response without re-enqueuing -- partners safely retry the replay
        request itself, not just the underlying event.


        Returns 202 with the new delivery row's id-shaped envelope. The terminal
        state of the delivery (delivered / failed / exhausted) is set
        asynchronously; partners poll the dashboard or GET /webhook-deliveries
        to watch.
      operationId: replay_event_events__event_id__replay_post
      parameters:
        - name: event_id
          in: path
          required: true
          schema:
            type: string
            description: >-
              Event id to replay, beginning with ``evt_``. Same
              404-on-anything-that-doesn't-match semantics as the single-event
              GET endpoint.
            title: Event Id
          description: >-
            Event id to replay, beginning with ``evt_``. Same
            404-on-anything-that-doesn't-match semantics as the single-event GET
            endpoint.
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: Idempotency-Key
      responses:
        '202':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectEventReplayResponse'
        '400':
          description: >-
            Partner has no enabled webhook endpoint configured. Set up a webhook
            URL first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '401':
          description: >-
            Authentication failed. ``error.code`` distinguishes
            ``authentication_required`` (missing / malformed / unknown key) from
            ``expired_credential`` (key matched but past its ``expires_at``).
            Rotate the key in the admin panel for the latter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '403':
          description: >-
            The key is valid but lacks permission for this endpoint.
            ``error.code`` is ``forbidden_scope`` when the key is missing a
            scope; ``account_pending_approval`` when the org is not yet active
            for Connect.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '404':
          description: Event not found in this tenant.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '409':
          description: Idempotency-Key was reused with a different body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '422':
          description: >-
            Validation failed. Runtime translates FastAPI / Pydantic validation
            errors into the same Connect envelope you see on 400s; this status
            is emitted when the request shape is structurally wrong (missing
            required field, wrong type).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '429':
          description: >-
            Rate limited. Back off and retry. ``error.code`` is
            ``rate_limited``; per-key throughput is documented via
            ``rate_limit_rps`` on the API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '500':
          description: >-
            Internal server error. ``error.code`` is ``internal_error``. The
            request can usually be safely retried with the same
            ``Idempotency-Key``.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
        '501':
          description: >-
            Endpoint is declared in the router but its implementation has not
            shipped yet (``error.code = not_yet_available``). Pinned in the
            schema so SDK generators emit the endpoint with a typed error rather
            than skipping it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectErrorEnvelope'
      security:
        - BearerAuth: []
components:
  schemas:
    ConnectEventReplayResponse:
      properties:
        id:
          type: string
          title: Id
          description: >-
            The new webhook_deliveries row id (UUID, rendered as string).
            Distinct from the original delivery's id if this event was delivered
            before; each replay produces a fresh row for audit clarity.
        object:
          type: string
          title: Object
          description: >-
            Always ``webhook_delivery``. Stripe-style discriminator so
            polymorphic partner-side handlers can dispatch.
        event_id:
          type: string
          title: Event Id
          description: >-
            The ``connect_events`` id the delivery row points at. Identical to
            the path-param ``event_id`` -- echoed for convenience.
        status:
          type: string
          title: Status
          description: >-
            Always ``pending`` on the response. The worker claims the row on its
            next tick and transitions it through ``processing`` to a terminal
            state (``delivered`` / ``failed`` / ``exhausted``).
        attempt_count:
          type: integer
          title: Attempt Count
          description: >-
            Always 0 on the response. The worker increments this on each
            attempt; a replay starts with a fresh budget.
      type: object
      required:
        - id
        - object
        - event_id
        - status
        - attempt_count
      title: ConnectEventReplayResponse
      description: |-
        Body of POST /connect/v1/events/:id/replay.

        Returns the newly-enqueued webhook_deliveries row's id and
        initial state. Partners poll their dashboard /
        GET /webhook-deliveries to watch the replay's lifecycle; the
        delivery's terminal state (delivered / failed / exhausted) is
        set asynchronously by the cron worker.
    ConnectErrorEnvelope:
      properties:
        error:
          $ref: '#/components/schemas/ConnectErrorBody'
          description: >-
            Error details. Always present on a non-2xx response from
            /connect/v1.
      type: object
      required:
        - error
      title: ConnectErrorEnvelope
      description: |-
        Top-level wrapper for every /connect/v1 failure response.

        Wire shape::

            {"error": {"type": ..., "code": ..., "message": ..., ...}}

        The wrapper is intentional: it keeps error bodies syntactically
        distinct from success bodies, so polymorphic partner code paths
        that read the same JSON for both states can branch on the
        presence of the ``error`` key.
    ConnectErrorBody:
      properties:
        type:
          type: string
          title: Type
          description: >-
            Stripe-style error category. One of: ``invalid_request_error``,
            ``authentication_error``, ``not_found_error``, ``rate_limit_error``,
            ``idempotency_error``, ``api_error``. Stable across minor versions;
            branch on this for top-level error routing.
          examples:
            - invalid_request_error
        code:
          type: string
          title: Code
          description: >-
            Machine-stable identifier for the specific failure. More specific
            than ``type``. Examples: ``invalid_request``,
            ``authentication_required``, ``expired_credential``,
            ``forbidden_scope``, ``account_pending_approval``,
            ``resource_not_found``, ``idempotency_conflict``, ``rate_limited``,
            ``internal_error``, ``not_yet_available``. Match on ``code`` in your
            client's error handler -- never parse ``message``.
          examples:
            - authentication_required
        message:
          type: string
          title: Message
          description: >-
            Human-readable explanation. Safe to surface in your own UI but may
            be reworded between releases -- never branch on the string.
          examples:
            - >-
              Missing or malformed Authorization header. Send: Authorization:
              Bearer sk_live_... or sk_test_...
        doc_url:
          type: string
          title: Doc Url
          description: >-
            Link to docs explaining this ``code``. Constructed as
            ``https://docs.zopay.cash/errors/<code>``. Surface this in support
            tickets so we can investigate without a round-trip.
          examples:
            - https://docs.zopay.cash/errors/authentication_required
        param:
          anyOf:
            - type: string
            - type: 'null'
          title: Param
          description: >-
            Names the offending input field when the error is
            input-shape-related (mostly ``invalid_request_error`` from 400 and
            422). Absent on auth, scope, rate-limit, and internal errors.
          examples:
            - currencies
      type: object
      required:
        - type
        - code
        - message
        - doc_url
      title: ConnectErrorBody
      description: |-
        The ``error`` object inside every /connect/v1 failure response.

        Mirrors ``ConnectError.to_envelope()`` in
        ``app/api/connect/errors.py``. Field set is locked -- adding a
        new field is a partner-visible contract change and requires a
        coordinated docs + schema update.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Connect API key (sk_live_… or sk_test_…)
      description: >-
        Paste your Connect API key (sk_live_… for production, sk_test_… for
        sandbox) without the ``Bearer `` prefix. Mint and rotate keys from the
        admin panel.

````