# Factur-AI External API LLM-optimized reference. Auto-generated from the live OpenAPI spec. Base URL: https://api.pagai.cl/api/v1/external ## Authentication Every endpoint except this one requires an API key: X-API-Key: Keys are created by a human in the dashboard (Settings > API keys) and shown once. There is no programmatic way to mint one, so an agent must be given a key up front. Each key carries a fixed set of scopes. ## Available Scopes - invoices:create: Create new invoices - invoices:read: Read invoice data - invoices:send: Send invoices and manual reminders - invoices:write: Cancel invoices - payments:write: Mark invoices as paid (external payments) - clients:create: Create and update clients - clients:read: Read client data - webhooks:manage: Manage webhook endpoints - *: Full access to all API features A newly created key does NOT get every scope by default. If you get a 403 with code `insufficient_scope`, read `details.required_scope` and ask the key owner to re-issue the key with it. ## Errors Every error shares one shape. Branch on `code` — it is stable. Do NOT match on `message`, which is prose and gets reworded. { "code": "invoice_not_found", "message": "No invoice with that id for this merchant.", "retry": "never", "details": null, "request_id": "3fa85f64-...", "path": "/api/v1/external/invoices/...", "timestamp": "2026-08-17T21:00:00Z" } `retry` tells you what to do automatically: - `never` — permanent. Retrying produces the same result. Fix the request. - `safe` — transient. Retry with exponential backoff. - `after_seconds:N` — wait at least N seconds before retrying. ### Code catalogue - checkout_not_found: HTTP 404, retry=never - client_already_exists: HTTP 409, retry=never - client_not_found: HTTP 404, retry=never - currency_conversion_failed: HTTP 503, retry=after_seconds:300 - currency_not_supported: HTTP 400, retry=never - idempotency_conflict: HTTP 409, retry=after_seconds:5 - insufficient_scope: HTTP 403, retry=never - invalid_api_key: HTTP 401, retry=never - invalid_provider: HTTP 400, retry=never - invoice_already_paid: HTTP 409, retry=never - invoice_invalid_state: HTTP 409, retry=never - invoice_not_found: HTTP 404, retry=never - invoice_not_sent: HTTP 409, retry=never - ip_not_allowed: HTTP 403, retry=never - merchant_misconfigured: HTTP 400, retry=never - missing_api_key: HTTP 401, retry=never - provider_credentials_missing: HTTP 400, retry=never - provider_not_configured: HTTP 400, retry=never - provider_session_expired: HTTP 410, retry=safe - provider_unavailable: HTTP 503, retry=safe - rate_limited: HTTP 429, retry=after_seconds:60 - validation_error: HTTP 422, retry=never - webhook_endpoint_invalid: HTTP 400, retry=never - webhook_endpoint_not_found: HTTP 404, retry=never A few endpoints return a different HTTP status than the catalogue default for historical reasons (a malformed UUID is 400 on invoice/client/checkout routes and 422 on webhook routes). `code` is consistent; prefer it. ## Conventions ### Retries and idempotency POST /invoices, POST /invoices/bulk, POST /checkout and POST /invoices/{invoice_id}/mark-paid accept an `Idempotency-Key` header (any unique string, e.g. a UUID). Send one on every write. Idempotency-Key: 9f8c1e2a-... Without it, a retry after a timeout creates a SECOND invoice. With it, the repeat call replays the original response. Keys are remembered 24 hours. Retrying while the first call is still running returns 409 `idempotency_conflict` — wait a few seconds and send the same key again. ### Already inside an MCP client? If you are Claude, Cursor or another MCP host, there is a ready-made server with six task-level tools over this API — it handles idempotency keys and error retries for you, so you do not have to implement what follows: claude mcp add pagai --env PAGAI_API_KEY= -- npx -y @pagai/mcp Use it for invoicing, payment links, checking payment and reminders. Come back to this reference for anything it does not cover: webhooks, bulk creation, and client management. ### Rate limits Per key, per minute. Every response carries: - X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset On 429 (`rate_limited`) a `Retry-After` header gives the seconds to wait. ### Pagination List endpoints take `limit` (1-100, default 50) and `offset` (default 0). There is no total count: request `limit + 1` and treat a short page as the end. Results are ordered newest-first for invoices, by name for clients. ### Money and dates Amounts are decimal strings, not floats (e.g. "119.00"). Dates are ISO-8601 (`YYYY-MM-DD`); timestamps are RFC-3339 UTC. --- ## POST /invoices **Summary**: Create invoice from ERP **Scope**: invoices:create **Description**: Create an invoice from an external ERP system using API key authentication. **Client handling**: If a client with the same email already exists it will be reused. Otherwise a new client is created. **Invoice sending**: Set `send_immediately: false` to keep the invoice as draft. **Idempotency**: send an `Idempotency-Key` header (any unique string, e.g. a UUID) to make retries safe. A repeat call with the same key replays the original response instead of doing the work again; keys are remembered for 24 hours. Retrying while the first call is still running returns `409`. ### Request Body (application/json) - client.name: string (required) - client.email: string (required) - client.rut: string (optional) — Chilean RUT or tax ID - client.phone: string (optional) - client.address: string (optional) - issue_date: string (optional) - due_date: string (required) - is_tax_exempt: boolean (optional) - tax_inclusive: boolean (optional) — If true, item prices already include tax (e.g. IVA). Tax will be extracted from totals instead of added on top. - tax_rate: number (optional) - currency: string (optional) - items[].description: string (required) - items[].quantity: number (optional) - items[].unit_price: number (required) - external_reference: string (optional) — Reference number from your system (e.g. ERP order ID) - send_immediately: boolean (optional) — If true, invoice will be sent immediately (number assigned, email queued) - locale: string (optional) — Language for invoice PDF and email ### Example request curl -X POST https://api.pagai.cl/api/v1/external/invoices \ -H 'X-API-Key: $API_KEY' \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "client": { "email": "pagos@agrocomercial.cl", "name": "Agrocomercial Ltda", "rut": "76.553.064-4" }, "due_date": "2026-09-16", "external_reference": "WOO-17222", "items": [ { "description": "Fertilizante 25kg", "quantity": 2, "unit_price": 19990 }, { "description": "Flete", "quantity": 1, "unit_price": 5000 } ] }' ### Headers - Idempotency-Key: string (optional) ### Response 201 - invoice_id: string (required) - invoice_number: string (optional) - external_reference: string (optional) - status: string (required) - total: string (required) - currency: string (required) - public_url: string (required) — URL to view/pay the invoice - client_id: string (required) - client_name: string (required) - created_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /invoices **Summary**: List invoices **Scope**: invoices:read **Description**: List invoices. ### Query Parameters - limit: integer (optional) (default: 50) - offset: integer (optional) (default: 0) - status: string (optional) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /invoices/bulk **Summary**: Bulk-create invoices from ERP **Scope**: invoices:create **Description**: Create up to **50 invoices** in a single call. This is a **best-effort** operation: a failure in one item does NOT abort the rest. Inspect each result's `success` field for per-item outcome. **Idempotency**: send an `Idempotency-Key` header (any unique string, e.g. a UUID) to make retries safe. A repeat call with the same key replays the original response instead of doing the work again; keys are remembered for 24 hours. Retrying while the first call is still running returns `409`. ### Request Body (application/json) - invoices[].client.name: string (required) - invoices[].client.email: string (required) - invoices[].client.rut: string (optional) — Chilean RUT or tax ID - invoices[].client.phone: string (optional) - invoices[].client.address: string (optional) - invoices[].issue_date: string (optional) - invoices[].due_date: string (required) - invoices[].is_tax_exempt: boolean (optional) - invoices[].tax_inclusive: boolean (optional) — If true, item prices already include tax (e.g. IVA). Tax will be extracted from totals instead of added on top. - invoices[].tax_rate: number (optional) - invoices[].currency: string (optional) - invoices[].items[].description: string (required) - invoices[].items[].quantity: number (optional) - invoices[].items[].unit_price: number (required) - invoices[].external_reference: string (optional) — Reference number from your system (e.g. ERP order ID) - invoices[].send_immediately: boolean (optional) — If true, invoice will be sent immediately (number assigned, email queued) - invoices[].locale: string (optional) — Language for invoice PDF and email ### Example request curl -X POST https://api.pagai.cl/api/v1/external/invoices/bulk \ -H 'X-API-Key: $API_KEY' \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "invoices": [ { "client": { "email": "a@example.cl", "name": "Cliente A" }, "due_date": "2026-09-16", "external_reference": "WOO-17222", "items": [ { "description": "Servicio", "unit_price": 10000 } ] }, { "client": { "email": "b@example.cl", "name": "Cliente B" }, "due_date": "2026-09-16", "external_reference": "WOO-17223", "items": [ { "description": "Servicio", "unit_price": 25000 } ] } ] }' ### Headers - Idempotency-Key: string (optional) ### Response 200 - total: integer (required) - succeeded: integer (required) - failed: integer (required) - results[].index: integer (required) — Zero-based index of this item in the request - results[].success: boolean (required) - results[].invoice.invoice_id: string (required) - results[].invoice.invoice_number: string (optional) - results[].invoice.external_reference: string (optional) - results[].invoice.status: string (required) - results[].invoice.total: string (required) - results[].invoice.currency: string (required) - results[].invoice.public_url: string (required) — URL to view/pay the invoice - results[].invoice.client_id: string (required) - results[].invoice.client_name: string (required) - results[].invoice.created_at: string (required) - results[].error: string (optional) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /invoices/{invoice_id}/send **Summary**: Send a draft invoice **Scope**: invoices:send **Description**: Send a draft invoice to the client (assigns an invoice number and queues an email notification). ### Path Parameters - invoice_id: string (required) ### Query Parameters - locale: string (optional) (default: es) ### Response 200 - invoice_id: string (required) - invoice_number: string (required) - status: string (required) - public_url: string (required) — URL to view/pay the invoice - sent_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /invoices/{invoice_id}/status **Summary**: Get invoice payment status **Scope**: invoices:read **Description**: Get detailed payment status for an invoice including reminder count. ### Path Parameters - invoice_id: string (required) ### Response 200 - invoice_id: string (required) - invoice_number: string (optional) - status: string (required) - total: string (required) - currency: string (required) - public_url: string (required) - issue_date: string (required) - due_date: string (required) - paid_at: string (optional) - reminders_sent: integer (optional) — Number of payment reminders sent - last_viewed_at: string (optional) - created_at: string (required) - updated_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /invoices/{invoice_id} **Summary**: Get invoice by ID **Scope**: invoices:read **Description**: Get a specific invoice. ### Path Parameters - invoice_id: string (required) ### Response 200 - id: string (required) - client_id: string (required) - client.id: string (required) - client.name: string (required) - client.email: string (optional) - client.phone: string (optional) - invoice_number: string (optional) - external_reference: string (optional) - status: enum('draft' | 'sent' | 'viewed' | 'paid' | 'overdue' | 'cancelled') (required) — Invoice status enum as defined in PRD. - currency: string (required) - exchange_rate: string (optional) - base_currency: string (optional) - issue_date: string (required) - due_date: string (required) - is_tax_exempt: boolean (required) - tax_inclusive: boolean (required) - tax_rate: string (required) - subtotal: string (required) - tax_total: string (required) - total: string (required) - amount_paid: string (optional) - public_token: string (required) - public_url: string (optional) - payment_proof_file_id: string (optional) - dte_folio: integer (optional) - dte_type: integer (optional) - dte_status: string (optional) - dte_track_id: string (optional) - is_legacy: boolean (optional) - payment_reference.provider: string (required) - payment_reference.transaction_id: string (required) - payment_reference.paid_at: string (optional) - items[].id: string (required) - items[].product_id: string (optional) - items[].description: string (required) - items[].quantity: string (required) - items[].unit_price: string (required) - items[].line_total: string (required) - created_at: string (required) - updated_at: string (required) - _links.self.href: string (required) — URL of the linked resource - _links.self.method: enum('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE') (optional) — HTTP method for this link - _links.self.rel: string (optional) — Relationship type - _links.self.title: string (optional) — Human-readable link description ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /invoices/{invoice_id}/mark-paid **Summary**: Mark invoice as paid (external payment) **Scope**: payments:write **Description**: Mark an invoice as paid when payment was received outside Factur-AI (e.g. bank transfer). This stops all automated reminders and triggers the `invoice.paid` webhook. **Valid invoice states**: `sent`, `viewed`, `overdue` **Idempotency**: send an `Idempotency-Key` header (any unique string, e.g. a UUID) to make retries safe. A repeat call with the same key replays the original response instead of doing the work again; keys are remembered for 24 hours. Retrying while the first call is still running returns `409`. ### Request Body (application/json) - payment_method: string (optional) — Informational payment method (e.g. 'bank_transfer', 'check') - paid_at: string (optional) — Ignored. The payment is always recorded at server time; the response returns that value. Kept for backwards compatibility. - notes: string (optional) — Optional payment notes or reference - payment_amount: number (optional) — Amount received. If provided, validated against invoice total. ### Path Parameters - invoice_id: string (required) ### Headers - Idempotency-Key: string (optional) ### Response 200 - invoice_id: string (required) - invoice_number: string (optional) - status: string (required) - paid_at: string (required) - payment_method: string (optional) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /invoices/{invoice_id}/remind **Summary**: Send manual payment reminder from ERP **Scope**: invoices:send **Description**: Trigger an ad-hoc payment reminder email to the client. **Valid invoice states**: `sent`, `viewed`, `overdue` ### Request Body (application/json) - custom_message: string (optional) — Optional custom message body for the reminder email ### Path Parameters - invoice_id: string (required) ### Response 200 - reminder_id: string (required) - invoice_id: string (required) - sent_at: string (required) - reminder_number: integer (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /invoices/{invoice_id}/cancel **Summary**: Cancel an invoice **Scope**: invoices:write **Description**: Cancel an invoice, stopping all automated reminders. - **Draft** invoices are deleted immediately. - **Sent / viewed / overdue** invoices are moved to `cancelled` state and the `invoice.cancelled` webhook is dispatched. - **Paid** invoices cannot be cancelled (returns 409). ### Request Body (application/json) - reason: string (optional) — Optional cancellation reason ### Path Parameters - invoice_id: string (required) ### Response 200 - invoice_id: string (required) - invoice_number: string (optional) - status: string (required) - cancelled_at: string (required) - reason: string (optional) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /clients **Summary**: Create client from ERP **Scope**: clients:create **Description**: Create a new client. Returns 409 if a client with the same email already exists. ### Request Body (application/json) - name: string (required) - email: string (required) - rut: string (optional) — Chilean RUT or tax ID - phone: string (optional) - address: string (optional) ### Example request curl -X POST https://api.pagai.cl/api/v1/external/clients \ -H 'X-API-Key: $API_KEY' \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "address": "Av. Providencia 1234, Santiago", "email": "pagos@agrocomercial.cl", "name": "Agrocomercial Ltda", "phone": "+56912345678", "rut": "76.553.064-4" }' ### Response 201 - id: string (required) - name: string (required) - email: string (optional) - rut: string (optional) - phone: string (optional) - address: string (optional) - created_at: string (required) - updated_at: string (required) - _links.self.href: string (required) — URL of the linked resource - _links.self.method: enum('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE') (optional) — HTTP method for this link - _links.self.rel: string (optional) — Relationship type - _links.self.title: string (optional) — Human-readable link description ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /clients **Summary**: List clients **Scope**: clients:read **Description**: List clients. ### Query Parameters - limit: integer (optional) (default: 50) - offset: integer (optional) (default: 0) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /clients/{client_id} **Summary**: Get client by ID **Scope**: clients:read **Description**: Get a specific client. ### Path Parameters - client_id: string (required) ### Response 200 - id: string (required) - name: string (required) - email: string (optional) - rut: string (optional) - phone: string (optional) - address: string (optional) - created_at: string (required) - updated_at: string (required) - _links.self.href: string (required) — URL of the linked resource - _links.self.method: enum('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE') (optional) — HTTP method for this link - _links.self.rel: string (optional) — Relationship type - _links.self.title: string (optional) — Human-readable link description ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## PATCH /clients/{client_id} **Summary**: Update client **Scope**: clients:create **Description**: Partially update a client. Only provided fields are changed. ### Request Body (application/json) - name: string (optional) - email: string (optional) - rut: string (optional) - phone: string (optional) - address: string (optional) ### Path Parameters - client_id: string (required) ### Response 200 - id: string (required) - name: string (required) - email: string (optional) - rut: string (optional) - phone: string (optional) - address: string (optional) - created_at: string (required) - updated_at: string (required) - _links.self.href: string (required) — URL of the linked resource - _links.self.method: enum('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE') (optional) — HTTP method for this link - _links.self.rel: string (optional) — Relationship type - _links.self.title: string (optional) — Human-readable link description ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /checkout **Summary**: Create checkout session **Scope**: checkout:create **Description**: Create a checkout session for a customer payment. Returns a URL to redirect your customer to for payment. The session creates an invoice internally, sends it immediately (assigns a number), and returns a checkout URL pointing to the hosted payment page. Your customer selects their preferred payment method on the Factur-AI checkout page and is redirected to `success_url` or `cancel_url` after payment. **Error contract**: failures share the same `{code, message, retry, request_id, details}` shape used by the public payment endpoints. See `docs/integration-errors.md`. **Idempotency**: send an `Idempotency-Key` header (any unique string, e.g. a UUID) to make retries safe. A repeat call with the same key replays the original response instead of doing the work again; keys are remembered for 24 hours. Retrying while the first call is still running returns `409`. ### Request Body (application/json) - amount: number (required) — Total amount in the specified currency - currency: string (optional) - description: string (required) — Line item description - customer_email: string (required) - customer_name: string (required) - success_url: string (required) — URL to redirect customer after successful payment - cancel_url: string (required) — URL to redirect customer if they cancel or payment fails - external_reference: string (optional) — Your system's order ID or reference ### Example request curl -X POST https://api.pagai.cl/api/v1/external/checkout \ -H 'X-API-Key: $API_KEY' \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount": 49990, "cancel_url": "https://tutienda.cl/pedido/17222/cancelado", "currency": "CLP", "customer_email": "cliente@example.cl", "customer_name": "Juan Perez", "description": "Pedido #17222", "external_reference": "WOO-17222", "success_url": "https://tutienda.cl/pedido/17222/gracias" }' ### Headers - Idempotency-Key: string (optional) ### Response 201 - checkout_id: string (required) - checkout_url: string (required) — URL to redirect your customer to for payment - status: string (required) - expires_at: string (required) ### Documented error statuses - 400, 422 — see the Errors section above for the response shape and codes. --- ## GET /checkout/{checkout_id}/status **Summary**: Get checkout session status **Scope**: checkout:create **Description**: Poll the status of a checkout session. ### Path Parameters - checkout_id: string (required) ### Response 200 - checkout_id: string (required) - status: string (required) — Checkout session status: pending, completed, expired - payment_status: string (required) — Underlying invoice payment status - external_reference: string (optional) - completed_at: string (optional) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /webhooks/events **Summary**: List available webhook events **Scope**: webhooks:manage **Description**: Get all available webhook event types. ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /webhooks/endpoints **Summary**: Create webhook endpoint **Scope**: webhooks:manage **Description**: Create a webhook endpoint. The signing secret is returned **only once**. ### Request Body (application/json) - url: string (required) — The HTTPS URL to receive webhook events - description: string (optional) - events[]: array[string] (optional) — List of events to subscribe to. Empty list = all events. - is_sandbox: boolean (optional) — Sandbox mode: deliveries are marked as test and don't affect stats or auto-disable. ### Response 201 - id: string (required) - url: string (required) - description: string (optional) - events[]: array[string] (required) - secret: string (required) — The webhook signing secret. Save it securely - it won't be shown again. - secret_prefix: string (required) - is_sandbox: boolean (optional) - created_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /webhooks/endpoints **Summary**: List webhook endpoints **Scope**: webhooks:manage **Description**: List all webhook endpoints. ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## GET /webhooks/endpoints/{endpoint_id} **Summary**: Get webhook endpoint **Scope**: webhooks:manage **Description**: Get a specific webhook endpoint. ### Path Parameters - endpoint_id: string (required) ### Response 200 - id: string (required) - url: string (required) - description: string (optional) - events[]: array[string] (required) - enabled: boolean (required) - secret_prefix: string (required) — Prefix of the webhook secret for identification - consecutive_failures: integer (required) - disabled_at: string (optional) - disabled_reason: string (optional) - is_sandbox: boolean (optional) - created_at: string (required) - updated_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## PATCH /webhooks/endpoints/{endpoint_id} **Summary**: Update webhook endpoint **Scope**: webhooks:manage **Description**: Update a webhook endpoint. ### Request Body (application/json) - url: string (optional) - description: string (optional) - events[]: array[string] (optional) - enabled: boolean (optional) - is_sandbox: boolean (optional) ### Path Parameters - endpoint_id: string (required) ### Response 200 - id: string (required) - url: string (required) - description: string (optional) - events[]: array[string] (required) - enabled: boolean (required) - secret_prefix: string (required) — Prefix of the webhook secret for identification - consecutive_failures: integer (required) - disabled_at: string (optional) - disabled_reason: string (optional) - is_sandbox: boolean (optional) - created_at: string (required) - updated_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## DELETE /webhooks/endpoints/{endpoint_id} **Summary**: Delete webhook endpoint **Scope**: webhooks:manage **Description**: Delete a webhook endpoint. ### Path Parameters - endpoint_id: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /webhooks/endpoints/{endpoint_id}/rotate-secret **Summary**: Rotate webhook secret **Scope**: webhooks:manage **Description**: Generate a new signing secret. The old secret is immediately invalidated. ### Path Parameters - endpoint_id: string (required) ### Response 200 - id: string (required) - url: string (required) - description: string (optional) - events[]: array[string] (required) - secret: string (required) — The webhook signing secret. Save it securely - it won't be shown again. - secret_prefix: string (required) - is_sandbox: boolean (optional) - created_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /webhooks/endpoints/{endpoint_id}/test **Summary**: Send test webhook **Scope**: webhooks:manage **Description**: Send a test event to verify endpoint configuration. ### Request Body (application/json) - event: string (optional) — Event type to simulate ### Path Parameters - endpoint_id: string (required) ### Response 200 - id: string (required) - endpoint_id: string (required) - event_type: string (required) - event_id: string (required) - status: enum('pending' | 'success' | 'failed' | 'retrying') (required) — Webhook delivery status. - attempts: integer (required) - max_attempts: integer (required) - next_retry_at: string (optional) - last_response_status: integer (optional) - last_error: string (optional) - completed_at: string (optional) - is_test: boolean (optional) - created_at: string (required) ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. --- ## POST /webhooks/verify-signature **Summary**: Verify webhook signature **Scope**: webhooks:manage **Description**: Debug tool to verify a webhook signature. Useful for testing your webhook receiver's HMAC implementation. Uses a relaxed 24-hour tolerance (vs 5 minutes in production). ### Request Body (application/json) - signature: string (required) — The X-Webhook-Signature header value (t=...,v1=...) - secret: string (required) — Your webhook signing secret (whsec_...) ### Response 200 - valid: boolean (required) — Whether the signature is valid - expected_signature: string (optional) — The expected signature (shown only when invalid) - message: string (required) — Human-readable result message ### Documented error statuses - 422 — see the Errors section above for the response shape and codes. ---