2026-05-22 18:30:59 +02:00
{% extends "base.html" %}
2026-05-22 19:04:06 +02:00
{% 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" >
2026-05-22 19:04:06 +02:00
{% 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 >
2026-05-22 19:04:06 +02:00
< 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" >
2026-05-22 19:04:06 +02:00
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" >
2026-05-22 19:04:06 +02:00
< 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 %}
2026-05-22 19:04:06 +02:00
<!-- 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" >
2026-05-22 19:04:06 +02:00
<!-- 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 >
2026-05-22 19:04:06 +02:00
< 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 >
2026-05-22 19:04:06 +02:00
< / 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 >
2026-05-22 19:04:06 +02:00
< 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 >
2026-05-22 19:04:06 +02:00
<!-- 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 >
2026-05-22 19:04:06 +02:00
< 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 >
2026-05-22 19:04:06 +02:00
< / 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 >
2026-05-22 19:04:06 +02:00
< 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 >
2026-05-22 19:04:06 +02:00
<!-- 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 >
2026-05-22 19:04:06 +02:00
< 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" >
2026-05-22 19:04:06 +02:00
< 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" >
2026-05-23 01:50:20 +02:00
Auth → bruger → bank-tilslutning → konti (v2) → transaktioner (v2) → webhooks.
2026-05-22 18:30:59 +02:00
< / p >
2026-05-22 19:04:06 +02:00
< 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 >
2026-05-23 01:50:20 +02:00
<!-- 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 %}