---
title: "Drain the webhook delivery DLQ"
method: POST
path: "/webhooks/dlq/drain"
tags: ["Webhooks"]
---

# Drain the webhook delivery DLQ

`POST /webhooks/dlq/drain`

Pulls failed webhook delivery events from the DLQ Pub/Sub subscription and re-enqueues them as Cloud Tasks for redelivery. Supports filtering by webhookId and tenantId. Use dryRun=true to preview without re-enqueuing. Requires the webhook:create permission and a tenant-id header. Note: the drain does a single bounded pull (maxMessages) and filters client-side, so on a DLQ dominated by other tenants a tenant-scoped drain may pull mostly non-matching messages and need to be re-run; increase maxMessages and/or repeat until the summary shows requeued==0 with an empty pull.

## Headers

- `tenant-id` string, required

## Request body

- DrainDlqRequest
  - `maxMessages` integer — Maximum number of messages to pull from the DLQ in this invocation. Integer between 1 and 1000; defaults to 100 when omitted.
  - `webhookId` string — Optional filter: only re-enqueue events for this webhook id. When omitted, events for all webhooks (within the tenant) are eligible.
  - `tenantId` string — Optional in the payload but always overridden by the tenant-id header for security; a caller-supplied value that differs from the header is ignored.
  - `targetQueue` string — Optional Cloud Tasks queue to re-enqueue matching events to. Must be an allowed webhook delivery queue; defaults to the primary webhook-delivery queue when omitted.
  - `dryRun` boolean — When true, pull and inspect matching messages but do not re-enqueue them (messages are nacked back to the subscription). Defaults to false.

## Response `200`

Drain summary: counts of messages pulled, re-enqueued, filtered out, and failed, plus per-message results. On a dry run, matching events are reported with status DRY_RUN and none are re-enqueued.

- DrainDlqResponse
  - `totalPulled` integer — Total messages pulled from the DLQ subscription in this drain.
  - `requeued` integer — Messages successfully re-enqueued to the target Cloud Tasks queue.
  - `filtered` integer — Messages skipped because they did not match the webhookId/tenantId filter.
  - `failed` integer — Messages that failed to re-enqueue; they remain in the DLQ and are retried on the next drain.
  - `results` DrainResult[] — Per-message outcome details for the messages pulled in this drain.
    - `messageId` string — Pub/Sub message id of the DLQ message.
    - `webhookId` string — Webhook id extracted from the failed event, when available.
    - `tenantId` string — Tenant id extracted from the failed event, when available.
    - `status` 'REQUEUED' | 'FILTERED' | 'DRY_RUN' | 'FAILED' — Outcome for this message. One of: REQUEUED (re-enqueued to the target queue), FILTERED (did not match the filter), DRY_RUN (matched but not re-enqueued because dryRun was true), FAILED (could not be re-enqueued or the payload was malformed).
    - `detail` string — Human-readable detail for the outcome, such as the target queue or the failure reason.

## Other responses

- `400` — Two distinct 400 shapes are possible. (1) The requested targetQueue is not an allowed webhook delivery queue: re-run with an allowed queue name (or omit targetQueue to use the default delivery queue); this returns the flat {"message": ...} body shown in the invalidTargetQueue example. (2) The request failed Bean Validation — the tenant-id header is missing, or maxMessages is outside the 1-1000 range: this returns the structured ApiError shape shown in the validationError example instead.
- `401` — Authentication is required or the JWT is missing/invalid. Retry with a valid bearer token.
- `403` — The caller lacks the webhook:create permission required to drain the DLQ.
- `502` — Pulling messages from the DLQ Pub/Sub subscription failed (for example, Pub/Sub was unavailable). Distinguishes a transient outage from an empty queue; retry the drain.

---

[API](https://skmtc.net/certifyos/apis/certify-api-layer.md) · [All operations](https://skmtc.net/certifyos/apis/certify-api-layer/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/certifyos/certify-api-layer/revisions/563848e0ecc0/schema)
