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

# Webhook retry worker

> Schedule webhook retries when you self-host Payoes.

This guide is for teams that deploy the `apps/web` stack themselves. Integrating merchants never touch `CRON_SECRET` — they only receive signed webhooks ([Webhooks](/guides/webhooks)).

## How retries work

Failed webhook deliveries retry up to five times (1 min → 5 min → 30 min → 2 h → 24 h).

Most of the time, retries run inline: whenever Payoes dispatches a new payment event, it processes any due retries first. That covers busy deployments without extra infrastructure.

On a quiet deployment, a failed delivery might sit until the next payment event triggers a retry pass. To avoid that gap, call `POST /api/cron/webhook-retries` on a schedule.

## `CRON_SECRET`

Set a bearer token in `apps/web/.env.local` or your production environment:

```bash theme={null}
CRON_SECRET=<openssl rand -base64 32>
```

Restart the app after changing env vars. The token protects the retry endpoint — requests without it get `401`.

You don't need a scheduler for local dev. To smoke-test the endpoint:

```bash theme={null}
curl -X POST http://localhost:3000/api/cron/webhook-retries \
  -H "Authorization: Bearer $CRON_SECRET"
```

Without `CRON_SECRET` set, the route returns `503`.

## Production endpoint

```bash theme={null}
curl -X POST https://your-payoes-host/api/cron/webhook-retries \
  -H "Authorization: Bearer $CRON_SECRET"
```

`X-Cron-Secret` works as an alternative to the `Authorization` header.

Response:

```json theme={null}
{ "processed": 3 }
```

`processed` is how many due deliveries were attempted in that run (capped at 25).

## Scheduling

Next.js doesn't run background jobs. Pick a scheduler and hit the endpoint every 1–5 minutes.

### Vercel Cron

Add `vercel.json` at the repository root (or app root, depending on your Vercel project):

```json theme={null}
{
  "crons": [
    {
      "path": "/api/cron/webhook-retries",
      "schedule": "*/5 * * * *"
    }
  ]
}
```

Add `CRON_SECRET` to your Vercel environment variables. Some Vercel plans don't attach `Authorization` automatically — if auth fails, trigger the endpoint from GitHub Actions or another scheduler instead.

### GitHub Actions

```yaml theme={null}
# .github/workflows/webhook-retries.yml
name: Webhook retries
on:
  schedule:
    - cron: "*/5 * * * *"
  workflow_dispatch:

jobs:
  retry:
    runs-on: ubuntu-latest
    steps:
      - name: Process due webhook retries
        run: |
          curl -fsS -X POST "${{ vars.PAYOES_URL }}/api/cron/webhook-retries" \
            -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}"
```

Put `CRON_SECRET` in repository secrets and `PAYOES_URL` in repository variables.

### Other schedulers

cron-job.org, Railway cron, Render cron, or a plain `crontab` on a VPS all work — any service that can `POST` with the bearer token.

## Troubleshooting

| Problem                             | What to check                                                   |
| ----------------------------------- | --------------------------------------------------------------- |
| `503 CRON_SECRET is not configured` | Env var missing in the deployment                               |
| `401 Unauthorized`                  | Token mismatch or wrong header                                  |
| Retries stall in production         | No scheduler and no payment events to trigger inline processing |
| `processed: 0`                      | Nothing was due at that moment                                  |
