# Webhooks in the sandbox (/en/docs/pix-processamento/sandbox/webhooks)

<QuickLinks>
  <QuickLink href="/docs/pix-processamento/sandbox" title="Sandbox" />

  <QuickLink href="/docs/pix-processamento/sandbox/credencial" title="Generate a credential" />

  <QuickLink href="/docs/pix-processamento/sandbox/cenarios" title="Test scenarios" />

  <QuickLink href="/docs/pix-processamento/webhooks" title="Webhooks" />
</QuickLinks>

In production, a webhook registered with [`POST /v1/user/webhooks`](/docs/pix-processamento/endpoints/webhooks/post_user_webhook) receives deliveries immediately. In the sandbox anyone can generate a credential and point a webhook anywhere, so delivery only starts after the URL proves it is yours:

1. On registration, the sandbox sends a `POST` to the URL with the token in the `X-Callback-Challenge` header and body `{}`.
2. The endpoint answers `2xx` echoing the token, either as the raw body or as `{"challenge":"<token>"}`.
3. Until it passes, the webhook stays `pending` and no delivery goes out.

The token travels only in the header, so a service that echoes the received body does not pass: only an endpoint that reads the header on purpose does. The URL must be public HTTPS; private addresses and redirects are refused.

<Flow>
  <FlowNode title="Sandbox" subtitle="POST + X-Callback-Challenge" />

  <FlowArrow label="your endpoint echoes the token" />

  <FlowNode title="verified" subtitle="deliveries start" />
</Flow>

```js
app.post('/webhook', (req, res) => {
  const challenge = req.get('X-Callback-Challenge');
  if (challenge) return res.status(200).json({ challenge });
  return handleCallback(req, res);
});
```

## Register the webhook [#register-the-webhook]

Register the URL with a secret to test the signature. The `secret` comes back only once in the response.

```bash
curl -X POST https://pix.sandbox.payzu.dev/v1/user/webhooks \
  -H "Authorization: Bearer $SANDBOX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://sua-app.com.br/webhook","generateSecret":true}'
```

Confirm ownership was verified before moving on: `GET /sandbox/webhooks/{id}/challenge` must answer `"status": "verified"`.

## Check and re-run the verification [#check-and-re-run-the-verification]

When a delivery does not happen, check the reason:

```bash
curl https://pix.sandbox.payzu.dev/sandbox/webhooks/$WEBHOOK_ID/challenge \
  -H "Authorization: Bearer $SANDBOX_TOKEN"
```

```json
{
  "webhookId": "cmh2k1x9d0001s6bwjb5bez01",
  "url": "https://sua-app.com.br/webhook",
  "status": "failed",
  "detail": "o endpoint respondeu 2xx sem ecoar o token, no corpo ou em {\"challenge\"}",
  "checkedAt": "2026-08-23T12:00:00.000Z"
}
```

`status` is `pending`, `verified` or `failed`. After fixing the endpoint, `POST /sandbox/webhooks/{id}/challenge` runs the check again. Deliveries follow the production format and signature: see [HMAC verification](/docs/pix-processamento/webhooks#verificação-hmac).