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:28:34 +02:00
|
|
|
|
Auth → bruger → bank-tilslutning → konti (v2) → transaktioner (v2) → webhooks.
|
|
|
|
|
|
Et komplet Tink-flow — klar til at blive bygget ind i jeres platform.
|
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>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 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 %}
|