walletdna 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- walletdna-1.0.0/PKG-INFO +96 -0
- walletdna-1.0.0/README.md +83 -0
- walletdna-1.0.0/pyproject.toml +22 -0
- walletdna-1.0.0/setup.cfg +4 -0
- walletdna-1.0.0/walletdna/__init__.py +11 -0
- walletdna-1.0.0/walletdna/client.py +141 -0
- walletdna-1.0.0/walletdna.egg-info/PKG-INFO +96 -0
- walletdna-1.0.0/walletdna.egg-info/SOURCES.txt +8 -0
- walletdna-1.0.0/walletdna.egg-info/dependency_links.txt +1 -0
- walletdna-1.0.0/walletdna.egg-info/top_level.txt +1 -0
walletdna-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: walletdna
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official WalletDNA SDK — wallet screening (allow / review / block), batch screening, and signed webhook verification.
|
|
5
|
+
Author: WalletDNA
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://walletdna.com/developer
|
|
8
|
+
Project-URL: Documentation, https://walletdna.com/docs/api
|
|
9
|
+
Project-URL: Source, https://github.com/wallet-dna/walletdna
|
|
10
|
+
Keywords: walletdna,wallet,screening,sanctions,ofac,aml,crypto,compliance,webhooks
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# walletdna (Python)
|
|
15
|
+
|
|
16
|
+
Official Python SDK for the [WalletDNA](https://walletdna.com) API — screen any wallet for sanctions and high‑risk exposure, get a clear **allow / review / block** decision, and verify signed webhooks. Standard library only, no dependencies.
|
|
17
|
+
|
|
18
|
+
API access requires an **Advanced** or **Enterprise** plan. Create an API key at <https://walletdna.com/developer>.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install walletdna
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Python 3.8+.
|
|
27
|
+
|
|
28
|
+
## Screen a wallet
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import os
|
|
32
|
+
from walletdna import WalletDNA
|
|
33
|
+
|
|
34
|
+
wdna = WalletDNA(os.environ["WALLETDNA_API_KEY"])
|
|
35
|
+
|
|
36
|
+
verdict = wdna.screen("0x722122dF12D4e14e13Ac3b6895a86e84145b6967")
|
|
37
|
+
print(verdict["decision"]) # "allow" | "review" | "block"
|
|
38
|
+
|
|
39
|
+
if verdict["decision"] == "block":
|
|
40
|
+
... # reject the deposit / withdrawal
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom thresholds & auto‑monitoring
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
verdict = wdna.screen(
|
|
47
|
+
address,
|
|
48
|
+
thresholds={"review": 40, "block": 75}, # map riskScore -> decision (sanctioned always blocks)
|
|
49
|
+
monitor=True, # also watch this wallet and webhook me on changes
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Batch (up to 25 addresses)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
resp = wdna.screen_batch([addr_a, addr_b, addr_c])
|
|
57
|
+
blocked = [r for r in resp["results"] if r.get("decision") == "block"]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Webhooks
|
|
61
|
+
|
|
62
|
+
Register an endpoint (the signing secret is returned **once** — store it):
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
hook = wdna.create_webhook(
|
|
66
|
+
"https://api.acme.com/walletdna",
|
|
67
|
+
events=["wallet.risk_changed", "wallet.sanctioned"],
|
|
68
|
+
)
|
|
69
|
+
secret = hook["secret"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Verify incoming events with the raw request body and the `WalletDNA-Signature` header (Flask example):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from flask import Flask, request, abort
|
|
76
|
+
from walletdna import verify_webhook
|
|
77
|
+
|
|
78
|
+
app = Flask(__name__)
|
|
79
|
+
|
|
80
|
+
@app.post("/walletdna")
|
|
81
|
+
def walletdna_webhook():
|
|
82
|
+
raw = request.get_data(as_text=True)
|
|
83
|
+
if not verify_webhook(raw, request.headers.get("WalletDNA-Signature", ""), WDNA_WEBHOOK_SECRET):
|
|
84
|
+
abort(400)
|
|
85
|
+
event = request.get_json()
|
|
86
|
+
# event["type"], event["data"] (a screening verdict + previousScore/label/reportUrl)
|
|
87
|
+
return "", 200
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Failed requests raise `WalletDNAError` with `.status`, `.code`, and `.body`. A `429` means you've hit your monthly API limit — add credits in the dashboard.
|
|
93
|
+
|
|
94
|
+
## Reference
|
|
95
|
+
|
|
96
|
+
Full OpenAPI spec: <https://walletdna.com/api/openapi> · Docs: <https://walletdna.com/docs/api>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# walletdna (Python)
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [WalletDNA](https://walletdna.com) API — screen any wallet for sanctions and high‑risk exposure, get a clear **allow / review / block** decision, and verify signed webhooks. Standard library only, no dependencies.
|
|
4
|
+
|
|
5
|
+
API access requires an **Advanced** or **Enterprise** plan. Create an API key at <https://walletdna.com/developer>.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install walletdna
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Python 3.8+.
|
|
14
|
+
|
|
15
|
+
## Screen a wallet
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import os
|
|
19
|
+
from walletdna import WalletDNA
|
|
20
|
+
|
|
21
|
+
wdna = WalletDNA(os.environ["WALLETDNA_API_KEY"])
|
|
22
|
+
|
|
23
|
+
verdict = wdna.screen("0x722122dF12D4e14e13Ac3b6895a86e84145b6967")
|
|
24
|
+
print(verdict["decision"]) # "allow" | "review" | "block"
|
|
25
|
+
|
|
26
|
+
if verdict["decision"] == "block":
|
|
27
|
+
... # reject the deposit / withdrawal
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Custom thresholds & auto‑monitoring
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
verdict = wdna.screen(
|
|
34
|
+
address,
|
|
35
|
+
thresholds={"review": 40, "block": 75}, # map riskScore -> decision (sanctioned always blocks)
|
|
36
|
+
monitor=True, # also watch this wallet and webhook me on changes
|
|
37
|
+
)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Batch (up to 25 addresses)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
resp = wdna.screen_batch([addr_a, addr_b, addr_c])
|
|
44
|
+
blocked = [r for r in resp["results"] if r.get("decision") == "block"]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Webhooks
|
|
48
|
+
|
|
49
|
+
Register an endpoint (the signing secret is returned **once** — store it):
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
hook = wdna.create_webhook(
|
|
53
|
+
"https://api.acme.com/walletdna",
|
|
54
|
+
events=["wallet.risk_changed", "wallet.sanctioned"],
|
|
55
|
+
)
|
|
56
|
+
secret = hook["secret"]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Verify incoming events with the raw request body and the `WalletDNA-Signature` header (Flask example):
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from flask import Flask, request, abort
|
|
63
|
+
from walletdna import verify_webhook
|
|
64
|
+
|
|
65
|
+
app = Flask(__name__)
|
|
66
|
+
|
|
67
|
+
@app.post("/walletdna")
|
|
68
|
+
def walletdna_webhook():
|
|
69
|
+
raw = request.get_data(as_text=True)
|
|
70
|
+
if not verify_webhook(raw, request.headers.get("WalletDNA-Signature", ""), WDNA_WEBHOOK_SECRET):
|
|
71
|
+
abort(400)
|
|
72
|
+
event = request.get_json()
|
|
73
|
+
# event["type"], event["data"] (a screening verdict + previousScore/label/reportUrl)
|
|
74
|
+
return "", 200
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Errors
|
|
78
|
+
|
|
79
|
+
Failed requests raise `WalletDNAError` with `.status`, `.code`, and `.body`. A `429` means you've hit your monthly API limit — add credits in the dashboard.
|
|
80
|
+
|
|
81
|
+
## Reference
|
|
82
|
+
|
|
83
|
+
Full OpenAPI spec: <https://walletdna.com/api/openapi> · Docs: <https://walletdna.com/docs/api>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "walletdna"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Official WalletDNA SDK — wallet screening (allow / review / block), batch screening, and signed webhook verification."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["walletdna", "wallet", "screening", "sanctions", "ofac", "aml", "crypto", "compliance", "webhooks"]
|
|
13
|
+
authors = [{ name = "WalletDNA" }]
|
|
14
|
+
dependencies = []
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://walletdna.com/developer"
|
|
18
|
+
Documentation = "https://walletdna.com/docs/api"
|
|
19
|
+
Source = "https://github.com/wallet-dna/walletdna"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["walletdna"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Official WalletDNA Python SDK.
|
|
2
|
+
|
|
3
|
+
Wallet screening (allow / review / block), batch screening, and signed
|
|
4
|
+
webhook verification. API access requires an Advanced or Enterprise plan —
|
|
5
|
+
create a key at https://walletdna.com/developer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .client import WalletDNA, WalletDNAError, verify_webhook
|
|
9
|
+
|
|
10
|
+
__all__ = ["WalletDNA", "WalletDNAError", "verify_webhook"]
|
|
11
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""WalletDNA API client — dependency-free (standard library only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.parse
|
|
11
|
+
import urllib.request
|
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://walletdna.com"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WalletDNAError(Exception):
|
|
18
|
+
"""Raised when the API returns a non-2xx response or the request fails."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, message: str, status: Optional[int] = None,
|
|
21
|
+
code: Optional[str] = None, body: Any = None):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
self.status = status
|
|
24
|
+
self.code = code
|
|
25
|
+
self.body = body
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WalletDNA:
|
|
29
|
+
"""Thin client for the WalletDNA API.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
wdna = WalletDNA(os.environ["WALLETDNA_API_KEY"])
|
|
33
|
+
verdict = wdna.screen("0x722122dF12D4e14e13Ac3b6895a86e84145b6967")
|
|
34
|
+
if verdict["decision"] == "block":
|
|
35
|
+
...
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL,
|
|
39
|
+
timeout: float = 60.0):
|
|
40
|
+
if not api_key or not isinstance(api_key, str):
|
|
41
|
+
raise WalletDNAError(
|
|
42
|
+
"An API key is required. Get one at https://walletdna.com/developer"
|
|
43
|
+
)
|
|
44
|
+
self.api_key = api_key
|
|
45
|
+
self.base_url = base_url.rstrip("/")
|
|
46
|
+
self.timeout = timeout
|
|
47
|
+
|
|
48
|
+
def _request(self, method: str, path: str,
|
|
49
|
+
body: Optional[Dict[str, Any]] = None) -> Any:
|
|
50
|
+
data = None if body is None else json.dumps(body).encode("utf-8")
|
|
51
|
+
req = urllib.request.Request(
|
|
52
|
+
f"{self.base_url}{path}",
|
|
53
|
+
data=data,
|
|
54
|
+
method=method,
|
|
55
|
+
headers={
|
|
56
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
62
|
+
raw = resp.read().decode("utf-8")
|
|
63
|
+
return json.loads(raw) if raw else None
|
|
64
|
+
except urllib.error.HTTPError as exc:
|
|
65
|
+
raw = exc.read().decode("utf-8", "replace")
|
|
66
|
+
parsed: Any = None
|
|
67
|
+
try:
|
|
68
|
+
parsed = json.loads(raw) if raw else None
|
|
69
|
+
except json.JSONDecodeError:
|
|
70
|
+
parsed = raw
|
|
71
|
+
msg = (parsed.get("message") or parsed.get("error")) if isinstance(parsed, dict) else f"HTTP {exc.code}"
|
|
72
|
+
raise WalletDNAError(
|
|
73
|
+
msg or f"HTTP {exc.code}",
|
|
74
|
+
status=exc.code,
|
|
75
|
+
code=parsed.get("error") if isinstance(parsed, dict) else None,
|
|
76
|
+
body=parsed,
|
|
77
|
+
) from None
|
|
78
|
+
except urllib.error.URLError as exc:
|
|
79
|
+
raise WalletDNAError(f"Request failed: {exc.reason}") from None
|
|
80
|
+
|
|
81
|
+
def screen(self, address: str, thresholds: Optional[Dict[str, int]] = None,
|
|
82
|
+
monitor: bool = False) -> Dict[str, Any]:
|
|
83
|
+
"""Screen a single address. Returns the verdict dict."""
|
|
84
|
+
out = self._request("POST", "/api/v1/screen", {
|
|
85
|
+
"address": address,
|
|
86
|
+
"thresholds": thresholds,
|
|
87
|
+
"monitor": monitor,
|
|
88
|
+
})
|
|
89
|
+
return out["result"]
|
|
90
|
+
|
|
91
|
+
def screen_batch(self, addresses: Sequence[str],
|
|
92
|
+
thresholds: Optional[Dict[str, int]] = None,
|
|
93
|
+
monitor: bool = False) -> Dict[str, Any]:
|
|
94
|
+
"""Screen up to 25 addresses. Returns the full response dict."""
|
|
95
|
+
return self._request("POST", "/api/v1/screen", {
|
|
96
|
+
"addresses": list(addresses),
|
|
97
|
+
"thresholds": thresholds,
|
|
98
|
+
"monitor": monitor,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
def list_webhooks(self) -> Dict[str, Any]:
|
|
102
|
+
return self._request("GET", "/api/webhooks")
|
|
103
|
+
|
|
104
|
+
def create_webhook(self, url: str,
|
|
105
|
+
events: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
106
|
+
"""Register a webhook endpoint. The signing secret is returned once."""
|
|
107
|
+
return self._request("POST", "/api/webhooks", {"url": url, "events": events})
|
|
108
|
+
|
|
109
|
+
def delete_webhook(self, webhook_id: str) -> Dict[str, Any]:
|
|
110
|
+
return self._request("DELETE", f"/api/webhooks/{urllib.parse.quote(webhook_id)}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def verify_webhook(raw_body: str, signature_header: str, secret: str,
|
|
114
|
+
tolerance_sec: int = 300, now: Optional[int] = None) -> bool:
|
|
115
|
+
"""Verify a WalletDNA-Signature header (format ``t=<unix>,v1=<hex>``).
|
|
116
|
+
|
|
117
|
+
Returns True only if the HMAC-SHA256 over ``f"{t}.{raw_body}"`` matches and
|
|
118
|
+
the timestamp is within ``tolerance_sec``. Pass the exact request body string.
|
|
119
|
+
"""
|
|
120
|
+
if not signature_header or not secret:
|
|
121
|
+
return False
|
|
122
|
+
parts = {}
|
|
123
|
+
for piece in signature_header.split(","):
|
|
124
|
+
if "=" in piece:
|
|
125
|
+
key, _, val = piece.partition("=")
|
|
126
|
+
parts[key.strip()] = val.strip()
|
|
127
|
+
try:
|
|
128
|
+
ts = int(parts.get("t", ""))
|
|
129
|
+
except ValueError:
|
|
130
|
+
return False
|
|
131
|
+
sig = parts.get("v1")
|
|
132
|
+
if not sig:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
current = int(time.time()) if now is None else now
|
|
136
|
+
if abs(current - ts) > tolerance_sec:
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
expected = hmac.new(secret.encode("utf-8"), f"{ts}.{raw_body}".encode("utf-8"),
|
|
140
|
+
hashlib.sha256).hexdigest()
|
|
141
|
+
return hmac.compare_digest(expected, sig)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: walletdna
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official WalletDNA SDK — wallet screening (allow / review / block), batch screening, and signed webhook verification.
|
|
5
|
+
Author: WalletDNA
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://walletdna.com/developer
|
|
8
|
+
Project-URL: Documentation, https://walletdna.com/docs/api
|
|
9
|
+
Project-URL: Source, https://github.com/wallet-dna/walletdna
|
|
10
|
+
Keywords: walletdna,wallet,screening,sanctions,ofac,aml,crypto,compliance,webhooks
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# walletdna (Python)
|
|
15
|
+
|
|
16
|
+
Official Python SDK for the [WalletDNA](https://walletdna.com) API — screen any wallet for sanctions and high‑risk exposure, get a clear **allow / review / block** decision, and verify signed webhooks. Standard library only, no dependencies.
|
|
17
|
+
|
|
18
|
+
API access requires an **Advanced** or **Enterprise** plan. Create an API key at <https://walletdna.com/developer>.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install walletdna
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Python 3.8+.
|
|
27
|
+
|
|
28
|
+
## Screen a wallet
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
import os
|
|
32
|
+
from walletdna import WalletDNA
|
|
33
|
+
|
|
34
|
+
wdna = WalletDNA(os.environ["WALLETDNA_API_KEY"])
|
|
35
|
+
|
|
36
|
+
verdict = wdna.screen("0x722122dF12D4e14e13Ac3b6895a86e84145b6967")
|
|
37
|
+
print(verdict["decision"]) # "allow" | "review" | "block"
|
|
38
|
+
|
|
39
|
+
if verdict["decision"] == "block":
|
|
40
|
+
... # reject the deposit / withdrawal
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom thresholds & auto‑monitoring
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
verdict = wdna.screen(
|
|
47
|
+
address,
|
|
48
|
+
thresholds={"review": 40, "block": 75}, # map riskScore -> decision (sanctioned always blocks)
|
|
49
|
+
monitor=True, # also watch this wallet and webhook me on changes
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Batch (up to 25 addresses)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
resp = wdna.screen_batch([addr_a, addr_b, addr_c])
|
|
57
|
+
blocked = [r for r in resp["results"] if r.get("decision") == "block"]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Webhooks
|
|
61
|
+
|
|
62
|
+
Register an endpoint (the signing secret is returned **once** — store it):
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
hook = wdna.create_webhook(
|
|
66
|
+
"https://api.acme.com/walletdna",
|
|
67
|
+
events=["wallet.risk_changed", "wallet.sanctioned"],
|
|
68
|
+
)
|
|
69
|
+
secret = hook["secret"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Verify incoming events with the raw request body and the `WalletDNA-Signature` header (Flask example):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from flask import Flask, request, abort
|
|
76
|
+
from walletdna import verify_webhook
|
|
77
|
+
|
|
78
|
+
app = Flask(__name__)
|
|
79
|
+
|
|
80
|
+
@app.post("/walletdna")
|
|
81
|
+
def walletdna_webhook():
|
|
82
|
+
raw = request.get_data(as_text=True)
|
|
83
|
+
if not verify_webhook(raw, request.headers.get("WalletDNA-Signature", ""), WDNA_WEBHOOK_SECRET):
|
|
84
|
+
abort(400)
|
|
85
|
+
event = request.get_json()
|
|
86
|
+
# event["type"], event["data"] (a screening verdict + previousScore/label/reportUrl)
|
|
87
|
+
return "", 200
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Errors
|
|
91
|
+
|
|
92
|
+
Failed requests raise `WalletDNAError` with `.status`, `.code`, and `.body`. A `429` means you've hit your monthly API limit — add credits in the dashboard.
|
|
93
|
+
|
|
94
|
+
## Reference
|
|
95
|
+
|
|
96
|
+
Full OpenAPI spec: <https://walletdna.com/api/openapi> · Docs: <https://walletdna.com/docs/api>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
walletdna
|