Files
tink-demo/src/templates/step6.html

272 lines
13 KiB
HTML
Raw Normal View History

2026-05-22 18:30:59 +02:00
{% extends "base.html" %}
{% block title %} — Step 6: Webhooks & Events{% endblock %}
2026-05-22 18:30:59 +02:00
{% 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", "Webhooks"] %}
2026-05-22 18:30:59 +02:00
{% 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
{% if i == 6 %}bg-violet-600 text-white font-semibold
{% elif i < 6 %}text-slate-300 hover:text-white
{% else %}text-slate-600 hover:text-slate-400{% endif %}">
<span class="w-5 h-5 rounded-full text-xs font-bold flex items-center justify-center
{% if i < 6 %}bg-slate-700 text-slate-300
{% elif i == 6 %}bg-violet-500 text-white
{% else %}bg-slate-800 text-slate-600{% endif %}">{{ i }}</span>
{{ step_names[i-1] }}
</a>
{% if i < 6 %}
<span class="text-slate-700"></span>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div class="mb-6">
<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">Webhooks & Real-time Events</h2>
<p class="text-slate-400 text-sm">Push-notifikationer til din backend</p>
2026-05-22 18:30:59 +02:00
</div>
</div>
<p class="text-slate-400 text-sm mt-2 max-w-2xl">
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.
2026-05-22 18:30:59 +02:00
</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>
2026-05-22 18:30:59 +02:00
<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>
2026-05-22 18:30:59 +02:00
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 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>
2026-05-22 18:30:59 +02:00
</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>
2026-05-22 18:30:59 +02:00
</div>
</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>
2026-05-22 18:30:59 +02:00
</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 %}
2026-05-22 18:30:59 +02:00
</div>
</div>
<!-- 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>
2026-05-22 18:30:59 +02:00
</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>
2026-05-22 18:30:59 +02:00
</div>
</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>
2026-05-22 18:30:59 +02:00
</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 %}
2026-05-22 18:30:59 +02:00
</div>
</div>
</div>
<!-- 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>
2026-05-22 18:30:59 +02:00
</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>
2026-05-22 18:30:59 +02:00
</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">
<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>
<p class="text-slate-500 text-sm mb-6 max-w-xl mx-auto">
Auth → bruger → bank-tilslutning → konti (v2) → transaktioner (v2) → webhooks.
2026-05-22 18:30:59 +02:00
</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"
2026-05-22 18:30:59 +02:00
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>
</div>
</div>
<!-- Next Steps for implementation -->
<div class="mt-6 bg-slate-900/60 border border-slate-800 rounded-2xl p-7">
<h4 class="text-white font-semibold text-base mb-4 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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
Næste skridt mod en produktion-klar integration
</h4>
<div class="grid md:grid-cols-2 gap-3 text-sm text-slate-400">
<div class="bg-slate-800/60 rounded-lg p-4 space-y-1.5">
<p class="text-violet-300 font-semibold text-xs uppercase tracking-wider mb-2">Backend (C# / .NET)</p>
<p>1. Lav en <code class="text-violet-300 font-mono text-xs">TinkApiClient</code> wrapper (brug <code class="text-xs font-mono">tink/client.py</code> som reference)</p>
<p>2. Gem <code class="text-violet-300 font-mono text-xs">external_user_id</code> + <code class="text-violet-300 font-mono text-xs">user_id</code> i din kundedatabase</p>
<p>3. Implementér <code class="text-violet-300 font-mono text-xs">/callback</code> endpoint med token exchange</p>
<p>4. Gem tokens sikkert (encrypted, server-side — ikke i cookie)</p>
</div>
<div class="bg-slate-800/60 rounded-lg p-4 space-y-1.5">
<p class="text-violet-300 font-semibold text-xs uppercase tracking-wider mb-2">Webhooks & Production</p>
<p>5. Byg webhook receiver med <a href="https://docs.tink.com/api#webhook/webhook-endpoints" target="_blank" class="text-violet-400 hover:text-violet-300 underline">HMAC-SHA256 signature verification</a></p>
<p>6. Skift til production Tink-credentials (Tink Console)</p>
<p>7. Registrér din production callback URI i Tink Console</p>
<p>8. Brug <code class="text-violet-300 font-mono text-xs">authorization-grant/delegate</code> i prod-flowet (ikke anon)</p>
</div>
</div>
<p class="text-xs text-slate-600 mt-4">
Kildekoden til dette demo (<code class="font-mono">src/tink/client.py</code> og <code class="font-mono">src/routes/demo.py</code>) er skrevet for at være letlæselig og direkte overførbar til andre platforme.
</p>
</div>
2026-05-22 18:30:59 +02:00
<!-- Navigation -->
<div class="mt-4 flex justify-start">
<a href="/demo/step/5"
class="px-4 py-2.5 border border-slate-700 text-slate-300 hover:text-white hover:border-slate-500 rounded-xl text-sm transition">
← Step 5
</a>
</div>
{% endblock %}