fix: step 6 webhooks demo - replace 404 events API with webhook registration flow
Some checks failed
Build and Deploy / deploy (push) Failing after 10m23s
Some checks failed
Build and Deploy / deploy (push) Failing after 10m23s
- Replace /events/v2/* endpoints (404 in sandbox) with /api/v1/webhooks - Add list_webhooks() and register_webhook() methods to TinkClient - Step 6 now shows: webhook flow diagram + curl examples + live API + sample payload - Handle sandbox 404 gracefully (shows example data, no red error) - Remove .env.production from git tracking (credentials via Gitea secrets) - deploy.yml: write .env.production from TINK_CLIENT_ID/SECRET secrets
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %} — Step 6: Events{% endblock %}
|
||||
{% block title %} — Step 6: Webhooks & Events{% endblock %}
|
||||
|
||||
{% block stepper %}
|
||||
<div class="bg-slate-900/60 border-b border-slate-800">
|
||||
<div class="max-w-6xl mx-auto px-4 py-3">
|
||||
<div class="flex items-center gap-1 overflow-x-auto">
|
||||
{% set step_names = ["Auth", "Opret Bruger", "Tilslut Bank", "Konti", "Transaktioner", "Events"] %}
|
||||
{% set step_names = ["Auth", "Opret Bruger", "Tilslut Bank", "Konti", "Transaktioner", "Webhooks"] %}
|
||||
{% for i in range(1, 7) %}
|
||||
<a href="/demo/step/{{ i }}"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm whitespace-nowrap transition
|
||||
@@ -32,118 +32,203 @@
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="w-8 h-8 rounded-full bg-violet-600 text-white text-sm font-bold flex items-center justify-center">6</span>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">Events v2</h2>
|
||||
<p class="text-slate-400 text-sm">Real-time Event Feed</p>
|
||||
<h2 class="text-xl font-bold text-white">Webhooks & Real-time Events</h2>
|
||||
<p class="text-slate-400 text-sm">Push-notifikationer til din backend</p>
|
||||
</div>
|
||||
<span class="ml-2 text-xs px-2 py-0.5 rounded-full font-mono font-semibold bg-violet-900/50 text-violet-300 border border-violet-700/40">v2 ✦</span>
|
||||
</div>
|
||||
<p class="text-slate-400 text-sm mt-2 max-w-2xl">
|
||||
Tink Events v2 giver real-time notifikationer når transaktioner bogføres.
|
||||
Kombineret med webhooks kan din applikation reagere på bankbevægelser øjeblikkeligt.
|
||||
Registrér dit endpoint hos Tink — så sender de automatisk en HTTP POST til dig
|
||||
hver gang en transaktion bogføres, opdateres eller en konto ændres. Zero polling.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div class="bg-red-950/50 border border-red-800/50 rounded-xl p-5 mb-6">
|
||||
<p class="text-red-300 text-sm font-semibold mb-1">Webhook API fejl</p>
|
||||
<pre class="text-red-400 text-sm font-mono whitespace-pre-wrap">{{ error }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- How it works banner -->
|
||||
<div class="bg-slate-900/50 border border-violet-800/30 rounded-xl p-5 mb-6">
|
||||
<h3 class="text-white font-semibold mb-3 text-sm">Hvordan webhooks virker</h3>
|
||||
<div class="flex flex-wrap items-center gap-2 text-sm">
|
||||
<div class="bg-slate-800 rounded-lg px-3 py-2 text-slate-300">
|
||||
<span class="text-violet-400 font-mono text-xs block mb-0.5">Din app</span>
|
||||
Brugeren kobler bank
|
||||
</div>
|
||||
<span class="text-slate-600 text-lg">→</span>
|
||||
<div class="bg-slate-800 rounded-lg px-3 py-2 text-slate-300">
|
||||
<span class="text-emerald-400 font-mono text-xs block mb-0.5">Tink</span>
|
||||
Henter transaktioner
|
||||
</div>
|
||||
<span class="text-slate-600 text-lg">→</span>
|
||||
<div class="bg-violet-900/50 border border-violet-700/50 rounded-lg px-3 py-2 text-slate-200 font-semibold">
|
||||
<span class="text-violet-300 font-mono text-xs block mb-0.5">POST /webhooks/tink</span>
|
||||
Dit endpoint modtager event
|
||||
</div>
|
||||
<span class="text-slate-600 text-lg">→</span>
|
||||
<div class="bg-slate-800 rounded-lg px-3 py-2 text-slate-300">
|
||||
<span class="text-amber-400 font-mono text-xs block mb-0.5">Din app</span>
|
||||
Opdaterer UI / notifikation
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
<!-- Booked transactions events -->
|
||||
<div class="space-y-3">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-white">Bogførte transaktioner</span>
|
||||
<code class="block text-xs text-emerald-400 font-mono mt-0.5">GET /events/v2/account-booked-transactions</code>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full bg-violet-900/50 text-violet-300 border border-violet-700/40 font-mono">v2</span>
|
||||
<!-- List webhooks -->
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-white">Registrerede webhooks</span>
|
||||
<code class="block text-xs text-emerald-400 font-mono mt-0.5">GET /api/v1/webhooks</code>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full bg-slate-800 text-slate-400 border border-slate-700 font-mono">app token</span>
|
||||
</div>
|
||||
<!-- curl -->
|
||||
<div class="px-4 py-3 border-b border-slate-800 bg-slate-950/40">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-slate-500 uppercase tracking-wider">cURL</span>
|
||||
<button onclick="copyToClipboard('curl-booked')"
|
||||
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800">Kopier</button>
|
||||
</div>
|
||||
<pre id="curl-booked" class="text-xs text-amber-300 font-mono whitespace-pre-wrap">{{ curl_booked }}</pre>
|
||||
</div>
|
||||
<!-- response -->
|
||||
<div class="p-4 max-h-96 overflow-y-auto">
|
||||
{% if result_booked %}
|
||||
{% if is_demo %}<p class="text-xs text-amber-400/70 mb-2 font-semibold">⚠ Sample Data</p>{% endif %}
|
||||
<pre class="raw-json">{{ result_booked | tojson(indent=2) }}</pre>
|
||||
{% else %}
|
||||
<p class="text-slate-500 text-sm text-center py-6">Ingen data — tilslut bank i Step 3 først.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="px-4 py-3 border-b border-slate-800 bg-slate-950/40">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-slate-500 uppercase tracking-wider">cURL</span>
|
||||
<button onclick="copyToClipboard('curl-list')"
|
||||
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800">Kopier</button>
|
||||
</div>
|
||||
<pre id="curl-list" class="text-xs text-amber-300 font-mono whitespace-pre-wrap">{{ curl_list }}</pre>
|
||||
</div>
|
||||
<div class="p-4 max-h-80 overflow-y-auto">
|
||||
{% if result_webhooks %}
|
||||
{% if is_demo %}<p class="text-xs text-amber-400/70 mb-2 font-semibold">⚠ Sample Data</p>{% endif %}
|
||||
<pre class="raw-json">{{ result_webhooks | tojson(indent=2) }}</pre>
|
||||
{% else %}
|
||||
<p class="text-slate-500 text-sm text-center py-6">Ingen webhooks registreret endnu.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All transaction events -->
|
||||
<div class="space-y-3">
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-white">Alle transaktionshændelser</span>
|
||||
<code class="block text-xs text-emerald-400 font-mono mt-0.5">GET /events/v2/account-transactions</code>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full bg-violet-900/50 text-violet-300 border border-violet-700/40 font-mono">v2</span>
|
||||
<!-- Register webhook -->
|
||||
<div class="bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-white">Registrér webhook endpoint</span>
|
||||
<code class="block text-xs text-emerald-400 font-mono mt-0.5">POST /api/v1/webhooks</code>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full bg-emerald-900/50 text-emerald-300 border border-emerald-800/50 font-mono">registreret ✓</span>
|
||||
</div>
|
||||
<!-- curl -->
|
||||
<div class="px-4 py-3 border-b border-slate-800 bg-slate-950/40">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-slate-500 uppercase tracking-wider">cURL</span>
|
||||
<button onclick="copyToClipboard('curl-all')"
|
||||
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800">Kopier</button>
|
||||
</div>
|
||||
<pre id="curl-all" class="text-xs text-amber-300 font-mono whitespace-pre-wrap">{{ curl_all }}</pre>
|
||||
</div>
|
||||
<!-- response -->
|
||||
<div class="p-4 max-h-96 overflow-y-auto">
|
||||
{% if result_all %}
|
||||
{% if is_demo %}<p class="text-xs text-amber-400/70 mb-2 font-semibold">⚠ Sample Data</p>{% endif %}
|
||||
<pre class="raw-json">{{ result_all | tojson(indent=2) }}</pre>
|
||||
{% else %}
|
||||
<p class="text-slate-500 text-sm text-center py-6">Ingen data — tilslut bank i Step 3 først.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="px-4 py-3 border-b border-slate-800 bg-slate-950/40">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-slate-500 uppercase tracking-wider">cURL</span>
|
||||
<button onclick="copyToClipboard('curl-register')"
|
||||
class="text-xs text-slate-400 hover:text-white transition px-2 py-1 rounded bg-slate-800">Kopier</button>
|
||||
</div>
|
||||
<pre id="curl-register" class="text-xs text-amber-300 font-mono whitespace-pre-wrap">{{ curl_register }}</pre>
|
||||
</div>
|
||||
<div class="p-4 max-h-80 overflow-y-auto">
|
||||
{% if webhook_registered %}
|
||||
{% if is_demo %}<p class="text-xs text-amber-400/70 mb-2 font-semibold">⚠ Sample Data</p>{% endif %}
|
||||
<pre class="raw-json">{{ webhook_registered | tojson(indent=2) }}</pre>
|
||||
{% else %}
|
||||
<p class="text-slate-500 text-sm text-center py-6">Webhook ikke registreret.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Webhook info box -->
|
||||
<div class="mt-6 bg-slate-900 border border-slate-800 rounded-xl p-5">
|
||||
<h3 class="text-white font-semibold mb-2 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-violet-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/></svg>
|
||||
Webhooks
|
||||
</h3>
|
||||
<p class="text-slate-400 text-sm mb-3">
|
||||
Konfigurer en webhook i Tink Console til at poste events til <code class="text-violet-300">/webhooks/tink</code> på dette endpoint.
|
||||
Events sendes i real-time når transaktioner opdateres.
|
||||
</p>
|
||||
<div class="bg-slate-950 rounded-lg p-3">
|
||||
<code class="text-xs text-emerald-400 font-mono">POST {{ app_base_url }}/webhooks/tink</code>
|
||||
<!-- Sample event payload -->
|
||||
<div class="mt-6 bg-slate-900 border border-slate-800 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-white">Sample Webhook Payload</span>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Sådan ser en event ud når Tink poster til dit endpoint</p>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full bg-amber-900/50 text-amber-300 border border-amber-800/50 font-mono">incoming POST</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 overflow-x-auto">
|
||||
<pre class="text-xs text-emerald-300 font-mono whitespace-pre">{
|
||||
"event": "account-booked-transaction:created",
|
||||
"context": {
|
||||
"userId": "a8b3c2d1-...",
|
||||
"externalUserId": "moneycapp-user-42"
|
||||
},
|
||||
"content": {
|
||||
"id": "tx_9f3a2b1c...",
|
||||
"accountId": "acc_7e1d4f2a...",
|
||||
"amount": {
|
||||
"currencyCode": "DKK",
|
||||
"value": { "scale": 2, "unscaledValue": "-24900" }
|
||||
},
|
||||
"dates": {
|
||||
"booked": "2025-05-22",
|
||||
"value": "2025-05-22"
|
||||
},
|
||||
"descriptions": {
|
||||
"display": "Netto Albertslund",
|
||||
"original": "NETTO ALBERTSLUND"
|
||||
},
|
||||
"merchantInformation": {
|
||||
"merchantCategoryCode": "5411",
|
||||
"merchantName": "Netto"
|
||||
},
|
||||
"status": "BOOKED",
|
||||
"types": { "type": "DEFAULT" }
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Your receiver endpoint -->
|
||||
<div class="mt-4 bg-slate-900/60 border border-slate-700/50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-8 h-8 rounded-full bg-emerald-600/20 border border-emerald-600/40 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-white text-sm font-semibold">Dit webhook modtager endpoint er live</p>
|
||||
<code class="text-emerald-400 text-xs font-mono">POST {{ app_base_url }}/webhooks/tink</code>
|
||||
</div>
|
||||
</div>
|
||||
<details class="text-sm text-slate-400">
|
||||
<summary class="cursor-pointer hover:text-white transition text-xs font-semibold uppercase tracking-wider text-slate-500">Se receiver kode</summary>
|
||||
<div class="mt-3 bg-slate-950 rounded-lg p-4 overflow-x-auto">
|
||||
<pre class="text-xs text-emerald-300 font-mono whitespace-pre">@router.post("/webhooks/tink")
|
||||
async def tink_webhook(request: Request):
|
||||
payload = await request.json()
|
||||
event_type = payload.get("event", "unknown")
|
||||
content = payload.get("content", {})
|
||||
|
||||
# Her ville MoneyCapp opdatere sin database,
|
||||
# sende push-notifikation, opdatere UI via SSE etc.
|
||||
|
||||
print(f"Tink event: {event_type}")
|
||||
print(f"Transaction ID: {content.get('id')}")
|
||||
print(f"Amount: {content.get('amount')}")
|
||||
|
||||
return {"status": "received"}</pre>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Final CTA -->
|
||||
<div class="mt-8 bg-gradient-to-br from-violet-900/30 to-indigo-900/20 border border-violet-700/30 rounded-2xl p-8 text-center">
|
||||
<h3 class="text-2xl font-bold text-white mb-2">Det var hele flowet 🎉</h3>
|
||||
<p class="text-slate-400 mb-6 max-w-lg mx-auto">
|
||||
Fra brugeroprettelse til live transaktioner og events — alt via Tink v2 API.
|
||||
Klar til at integrere i MoneyCapp.
|
||||
<div class="text-4xl mb-3">🎉</div>
|
||||
<h3 class="text-2xl font-bold text-white mb-2">Det var hele flowet</h3>
|
||||
<p class="text-slate-400 mb-2 max-w-lg mx-auto">
|
||||
Fra brugeroprettelse over live bankdata til real-time webhooks — alt via Tink API.
|
||||
</p>
|
||||
<div class="flex gap-3 justify-center">
|
||||
<a href="/" class="px-5 py-2.5 border border-slate-600 text-slate-300 hover:text-white hover:border-slate-400 rounded-xl text-sm transition">Kør demo igen</a>
|
||||
<a href="https://docs.tink.com" target="_blank"
|
||||
<p class="text-slate-500 text-sm mb-6 max-w-xl mx-auto">
|
||||
Vi har vist: auth → bruger → bank-tilslutning → konti (v2) → transaktioner (v2) → webhooks.
|
||||
Det er præcis hvad MoneyCapp mangler for at gøre deres integration robust.
|
||||
</p>
|
||||
<div class="flex gap-3 justify-center flex-wrap">
|
||||
<a href="/demo/reset" class="px-5 py-2.5 border border-slate-600 text-slate-300 hover:text-white hover:border-slate-400 rounded-xl text-sm transition">↺ Kør demo igen</a>
|
||||
<a href="https://docs.tink.com/api-introduction" target="_blank"
|
||||
class="px-5 py-2.5 bg-violet-600 hover:bg-violet-500 text-white rounded-xl text-sm font-semibold transition">
|
||||
Tink Docs →
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user