Skip to main content

Workspace webhooks

Some events belong to the workspace, not to a WhatsApp number: a phone line was renewed, a wallet top-up was paid. They go to workspace webhooks, a registry separate from the number webhooks. Workspace webhooks are the same registry as the phone line webhooks, seen from a wider surface:
  • /v1/phone-lines/webhooks accepts only phone-line events.
  • /v1/workspace/webhooks accepts every workspace event: the phone-line events and wallet.topup_paid. It lists every webhook of the workspace, including the ones created on the phone-lines surface.
Same signing secret (plwh_), same signature, same retries and the same delivery log on both. A number webhook — even one subscribed to "*" — never receives these events.

Configure

  • In the dashboard: Workspace keys (/workspace-api-keys), section Workspace webhooks, as an Owner or Admin. Webhooks listening only to phone-line events also show under Phone lines → Line webhooks.
  • Through the API: the endpoints below, with a workspace key or an account key with x-workspace-id. A number key gets 403 NUMBER_SCOPE_NOT_ALLOWED. With an account key or an OAuth/MCP token, the user must be an Owner or Admin (403 PERMISSION_DENIED otherwise).
The request and response shapes, the name and URL rules and the errors are the same as on /v1/phone-lines/webhooks — only the accepted events differ.

Create a webhook for paid top-ups

The secret comes only in this response and has no rotation. Store it before anything else. Creating is not idempotent: a repeated POST creates a second webhook.

"*" and wallet events

"*" means every phone-line event, and only those. Wallet events are opt-in: a webhook receives wallet.topup_paid only if it names it in events. That keeps a webhook created for phone lines with "*" from starting to receive money events it never asked for. To get everything, list both: ["*", "wallet.topup_paid"].
A webhook that names a wallet event can be read, deleted and have its deliveries listed on /v1/phone-lines/webhooks too, but editing it there is refused with 400 INVALID_WEBHOOK, because that surface does not know wallet events. Edit it on /v1/workspace/webhooks.

Events

Fires once per credited top-up order, on every rail: Pix, the hosted Stripe page, a transparent checkout, and also top-ups made in the dashboard. A redelivery or a duplicate confirmation from the payment provider does not fire it again. Charges made by the platform — renewals, numbers, lines — and balance adjustments by the Pilot Status team do not fire it.
Match the top-up by orderId, and dedupe deliveries by the envelope’s id (also sent as the Idempotency-Key header). The webhook is sent only after the order is completed: when it arrives, GET /v1/billing/topups/{id} already answers status: "paid".

Payload, signature and retries

Every delivery is a POST with { id, event, createdAt, data }, signed with x-pilot-status-signature (hex HMAC-SHA256 of the raw body with the webhook’s secret), with Idempotency-Key equal to id. Each delivery gets 6 attempts in total, over about 15 minutes. Verification code and details: Phone line webhooks → Verify the signature and Delivery and retries.