Importar facturas a Quipu
Mismo patrón que la integración con Holded: escuchamos el evento invoice.created y empujamos la factura a Quipu vía su API REST.
Qué necesitas
- API key de BuildNexion con scope
read:invoices. - App key + secret de Quipu API.
- Endpoint público HTTPS.
Paso 1 — Registrar el webhook
bash
curl -X POST https://api.buildnexion.com/v1/webhooks \
-H "Authorization: Bearer bn_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/buildnexion/quipu-sync",
"events": ["invoice.created", "invoice.updated"]
}'Paso 2 — Token OAuth de Quipu
Quipu usa OAuth2 con client_credentials. Pide un token y cacharrale en memoria durante 1 hora:
python
import os, time, requests
_token_cache = {"value": None, "expires_at": 0}
def quipu_token() -> str:
if _token_cache["value"] and _token_cache["expires_at"] > time.time():
return _token_cache["value"]
res = requests.post(
"https://getquipu.com/oauth/token",
auth=(os.environ["QUIPU_APP_ID"], os.environ["QUIPU_APP_SECRET"]),
data={"grant_type": "client_credentials"},
)
body = res.json()
_token_cache["value"] = body["access_token"]
_token_cache["expires_at"] = time.time() + body["expires_in"] - 30
return _token_cache["value"]Paso 3 — Endpoint del webhook
python
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
import os, json, requests
from verify import verify_buildnexion_signature
app = FastAPI()
@app.post("/buildnexion/quipu-sync")
async def webhook(request: Request, bg: BackgroundTasks):
raw = (await request.body()).decode("utf-8")
sig = request.headers.get("buildnexion-signature", "")
if not verify_buildnexion_signature(
payload=raw,
signature_header=sig,
secret=os.environ["BUILDNEXION_WEBHOOK_SECRET"],
):
raise HTTPException(401, "invalid signature")
event = json.loads(raw)
if event["type"] in ("invoice.created", "invoice.updated"):
bg.add_task(sync_to_quipu, event["data"]["object"])
return {"ok": True}Paso 4 — Crear o actualizar el gasto en Quipu
python
def sync_to_quipu(invoice: dict):
bn_key = os.environ["BUILDNEXION_API_KEY"]
provider = requests.get(
f"https://api.buildnexion.com/v1/providers/{invoice['provider_id']}",
headers={"Authorization": f"Bearer {bn_key}"},
).json()
headers = {
"Authorization": f"Bearer {quipu_token()}",
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
}
# BuildNexion guarda céntimos; Quipu pide decimales como string
subtotal = f"{invoice['subtotal_amount'] / 100:.2f}"
total = f"{invoice['total_amount'] / 100:.2f}"
payload = {
"data": {
"type": "expenses",
"attributes": {
"issue_date": invoice["issue_date"],
"number": invoice["invoice_number"],
"subtotal": subtotal,
"total": total,
"supplier_name": provider["name"],
"supplier_tin": provider["nif"],
}
}
}
requests.post(
"https://getquipu.com/api/expenses",
headers=headers,
json=payload,
)Como con Holded: añade idempotencia guardando
event.id. Si reintentamos, no duplicarás el gasto en Quipu.Errores típicos
- Quipu devuelve 422 — número de factura duplicado para el mismo proveedor. Decide si actualizar o ignorar.
- Token Quipu caduca — invalida el caché si recibes 401 y reintenta.
- Importes mal sumados — recuerda que BuildNexion devuelve enteros en céntimos.