Skip to main content
Tiempo de lectura: ~5 minutos.
timbrix/sdk-php es el cliente oficial de Timbrix para PHP 8.1 o superior. No depende de ningún framework (solo de Guzzle), devuelve modelos de solo lectura con autocompletado y lanza excepciones tipadas en lugar de errores genéricos de HTTP.
No uses tu API key en código que corre en el navegador: cualquiera que abra la página podría verla. Llama al SDK desde tu servidor.

Instalación

Quickstart

Sigue el mismo flujo que en Quickstart: regístrate en app.timbrix.mx/register, copia tu API key de sandbox desde Configuración → API Keys, y crea cliente → producto → CFDI en ese orden.
Para pasar a producción, cambia la API key de sandbox por una de producción: la URL base, los endpoints y las respuestas son idénticos en ambos entornos.

Opciones del cliente

También acepta httpClient (un GuzzleHttp\ClientInterface propio, por ejemplo con un proxy). El SDK envía el canal sdk en el header X-Timbrix-Client, que queda registrado en cada CFDI como channel. Una opción desconocida lanza InvalidArgumentException. Cada API key pertenece a una sola organización; para emitir con otro RFC emisor, usa una API key de esa organización.

Idempotencia y reintentos

Un CFDI duplicado es un problema fiscal real, así que el SDK es estricto con qué reintenta:
  • Solo se reintenta ante errores de red, 429, 5xx y, con idempotencyKey, el 409 PAC_STAMP_PENDING, con espera exponencial y respetando Retry-After. Por defecto son 2 reintentos (maxRetries).
  • GET y DELETE se reintentan siempre.
  • invoices->create e invoices->registerPayment se reintentan solo si pasas idempotencyKey. Sin ella, un fallo de red o un 5xx llega como excepción y el SDK no repite el timbrado.
  • cancel, sendEmail y los POST/PUT de clientes y productos nunca se reintentan automáticamente. previewPdf sí, porque no timbra nada.
  • El SDK no inventa una idempotencyKey: usa una llave estable que tú controles, como el id de tu pedido. Con la misma llave, un reintento devuelve el CFDI ya timbrado en vez de crear otro.
  • Con idempotencyKey, un 409 con código PAC_STAMP_PENDING (otra petición con la misma llave sigue en curso) también se reintenta. Con el maxRetries por defecto (2) el SDK espera solo unos segundos, así que un PAC_STAMP_PENDING que dure más llega como ConflictException: reintenta más tarde (por ejemplo, desde un job en cola) con la misma idempotencyKey.
Si la API responde PAC_STAMP_UNCONFIRMED, no se sabe todavía si el PAC timbró. Llega como ConflictException y no se reintenta: espera el webhook invoice.stamped o invoice.stamping_failed, o vuelve a intentar más tarde con la misma idempotencyKey. Del mismo modo, PAC_CANCEL_UNCONFIRMED al cancelar significa que no se sabe si el SAT procesó la cancelación: consulta la factura con $timbrix->invoices->get($uuid) antes de intentarlo de nuevo.

Manejo de errores

Toda petición fallida lanza una subclase de Timbrix\Exceptions\TimbrixException, nunca una excepción de Guzzle. Todas exponen getStatusCode() (0 si no hubo respuesta), getErrorCode() (el código de dominio de la API), getSuggestion(), getMessages() y getBody(). RateLimitException agrega getRetryAfter().

Verificar webhooks

Timbrix firma el body exacto de cada webhook con HMAC-SHA256 y lo manda en el header X-Webhook-Signature con el formato sha256=<hex>. Webhook::constructEvent verifica la firma y devuelve el evento ya decodificado. Pásale el body crudo, tal como llegó: si lo decodificas y lo vuelves a serializar, la firma ya no coincide.
Excluye esta ruta de la protección CSRF (VerifyCsrfToken en Laravel 10 o anterior, validateCsrfTokens(except: [...]) en Laravel 11), porque Timbrix no envía el token. Si solo necesitas saber si la firma es válida, Webhook::verify($payload, $signature, $secret) devuelve un booleano y nunca lanza excepciones.
La firma no cubre el header X-Webhook-Timestamp, así que no sirve para detectar repeticiones. Deduplica las entregas por X-Webhook-ID: un reintento de la misma entrega conserva el mismo id. Guarda los ids ya procesados y descarta los repetidos.
Para los eventos disponibles y la forma de cada payload, ver Create Webhook.

Laravel

El SDK funciona hoy en Laravel con el ejemplo de arriba. Un paquete dedicado para Laravel (service provider y facade) está planeado, sin fecha definida.

Qué no incluye la v1

  • Series y webhooks: se configuran una sola vez desde el panel. Al timbrar, eliges la serie por nombre con series en el payload. Ver Series y folios.
  • Autenticación: solo API key. No hay soporte de bearer token ni OAuth.
  • Administración: organizaciones, API keys, usuarios y logs se manejan desde el panel o el CLI de Timbrix.
  • Exportaciones masivas y reportes.

Referencia completa

El README del paquete lista cada recurso y método. Para el detalle de cada endpoint REST subyacente, ver API Reference.