Files
tink-demo/src/templates/step6.html
Henrik Jess Nielsen f6d17ae3c5
All checks were successful
Build and Deploy / deploy (push) Successful in 49s
fix: customer-facing cleanup — remove internal branding, print→logging, dynamic dates, sandbox note, next steps
- main.py: neutral API description (remove 'sales demo')
- base.html: 'Open Banking Demo' nav, neutral footer
- demo.py: /debug-session gated behind DEMO_MODE, all print() → logging,
  webhook receiver has C# signature verification example + Tink docs link,
  removed duplicate import
- demo_data.py: all hardcoded 2026 dates replaced with dynamic date helpers
- step2.html: external_user_id terminology (remove tink_external_ref)
- step.html: sandbox note on Step 3 (anonymous vs production flow)
- step6.html: 'Next Steps' section for C#/.NET implementation
2026-05-23 01:50:20 +02:00

272 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% 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", "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
{% 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>
</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.
</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">
<!-- 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>
</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>
<!-- 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>
</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>
<!-- 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">
<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.
</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>
</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>
<!-- 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 %}