@pimia/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,6 +92,63 @@ if (meta.idempotentReplay) {
92
92
  }
93
93
  ```
94
94
 
95
+ ## Recibir webhooks
96
+
97
+ `verifyWebhook` comprueba la firma `PIMIA-WEBHOOK-v1` y te devuelve el evento
98
+ tipado. No reimplementes el HMAC:
99
+
100
+ ```ts
101
+ import express from 'express'
102
+ import { verifyWebhook, WebhookVerificationError } from '@pimia/sdk'
103
+
104
+ // ⚠️ express.raw(), NO express.json(): Pimia firma los bytes que envía, y
105
+ // parsear + volver a serializar rompe la firma sin que se vea por qué.
106
+ app.post('/pimia', express.raw({ type: 'application/json' }), async (req, res) => {
107
+ let hook
108
+
109
+ try {
110
+ hook = await verifyWebhook({
111
+ secret: process.env.PIMIA_WEBHOOK_SECRET,
112
+ headers: req.headers,
113
+ body: req.body,
114
+ })
115
+ } catch (error) {
116
+ return res.status(400).send((error as WebhookVerificationError).reason)
117
+ }
118
+
119
+ // Pimia reintenta: la misma entrega llega con el mismo `delivery`.
120
+ // Procesar cada uno una sola vez es todo el exactly-once que necesitas.
121
+ if (await yaProcesado(hook.delivery)) return res.sendStatus(200)
122
+
123
+ if (hook.known) {
124
+ switch (hook.event) {
125
+ case 'estimate.accepted':
126
+ await facturar(hook.payload.id) // payload tipado, sin castings
127
+ break
128
+ case 'invoice.paid':
129
+ await cobrar(hook.payload.id)
130
+ break
131
+ }
132
+ }
133
+
134
+ res.sendStatus(200) // responde rápido; el trabajo pesado, a una cola
135
+ })
136
+ ```
137
+
138
+ Los ocho eventos del catálogo (`approval.decided`, `invoice.received`,
139
+ `app.revoked`, `customer.created`, `customer.updated`, `invoice.created`,
140
+ `estimate.accepted`, `invoice.paid`) vienen tipados. Uno que este SDK todavía
141
+ no conozca **no es un error**: se verifica igual y llega con `known: false`.
142
+
143
+ Detalles que ahorran un rato:
144
+
145
+ - `secret` acepta una **lista** de secretos, para rotarlo sin ventana de caída.
146
+ - La ventana anti-replay son 300 s; ajústala con `toleranceSeconds`.
147
+ - Los errores traen un `reason` (`signature_mismatch`, `timestamp_out_of_window`,
148
+ `missing_headers`, `invalid_timestamp`, `invalid_json`) para tus métricas.
149
+ - `signWebhook()` firma un cuerpo como lo haría Pimia: úsalo en **tus tests**,
150
+ no en producción.
151
+
95
152
  ## Más
96
153
 
97
154
  Documentación completa, modelo mental (un tenant = una base URL = un token),