@volter/twin-veriff 0.1.0
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.
- package/LICENSE +202 -0
- package/README.md +247 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +30 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +119 -0
- package/dist/src/veriff-budget.d.ts +55 -0
- package/dist/src/veriff-budget.js +151 -0
- package/dist/src/veriff-capabilities.d.ts +10 -0
- package/dist/src/veriff-capabilities.js +1079 -0
- package/dist/src/veriff-conformance.d.ts +28 -0
- package/dist/src/veriff-conformance.js +223 -0
- package/dist/src/veriff-connector.d.ts +133 -0
- package/dist/src/veriff-connector.js +372 -0
- package/dist/src/veriff-events.d.ts +62 -0
- package/dist/src/veriff-events.js +82 -0
- package/dist/src/veriff-server.d.ts +27 -0
- package/dist/src/veriff-server.js +101 -0
- package/dist/src/veriff-signature.d.ts +36 -0
- package/dist/src/veriff-signature.js +55 -0
- package/dist/src/veriff-twin.d.ts +104 -0
- package/dist/src/veriff-twin.js +759 -0
- package/package.json +65 -0
- package/src/cli.ts +29 -0
- package/src/index.ts +172 -0
- package/src/veriff-budget.ts +177 -0
- package/src/veriff-capabilities.ts +1157 -0
- package/src/veriff-conformance.ts +264 -0
- package/src/veriff-connector.ts +406 -0
- package/src/veriff-events.ts +117 -0
- package/src/veriff-server.ts +112 -0
- package/src/veriff-signature.ts +86 -0
- package/src/veriff-twin.ts +795 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# @volter/twin-veriff
|
|
2
|
+
|
|
3
|
+
A local, stateful, vendor-faithful twin of **Veriff's Public API v1** — the identity-verification
|
|
4
|
+
Station API (`stationapi.veriff.com` / `api.veriff.me`). Point your unmodified KYC client at it and
|
|
5
|
+
get vendor-correct responses — sessions, the `created → submitted → approved/declined` decision
|
|
6
|
+
lifecycle, HMAC-signed decision and event webhooks, attempts, media and the vendor's exact error
|
|
7
|
+
envelope — entirely offline, with no real API key, no real end user and no billed verification.
|
|
8
|
+
Built on the shared [`@volter/world-core`](../../..) kernel; see [the model](../../../docs/concepts/the-model.md) for storage and branching, not an
|
|
9
|
+
ad-hoc mock.
|
|
10
|
+
|
|
11
|
+
**The API URL is account-specific by design.** Veriff's reference tells you to read your BaseURL off
|
|
12
|
+
the Customer Portal, and its OpenAPI `servers` entry is the placeholder `https://example-base-url`.
|
|
13
|
+
Two hosts appear in official material — `stationapi.veriff.com` (the media code samples, and the
|
|
14
|
+
real consumer) and `api.veriff.me` (`@veriff/js-sdk` v2.0.0's compiled default) — and the injection
|
|
15
|
+
map routes **both** here. The paths are identical either way.
|
|
16
|
+
|
|
17
|
+
**The HMAC is real, not faked.** `X-AUTH-CLIENT` is accepted-but-never-checked (a twin fakes auth
|
|
18
|
+
locally), but `X-HMAC-SIGNATURE` is verified for real, including the vendor's asymmetric payload
|
|
19
|
+
rule: **POST/PATCH sign the request body; GET/DELETE sign the resource id in the path** — the
|
|
20
|
+
*attempt* id for `/v1/attempts/{id}/media`, the *media* id for `/v1/media/{id}`. The implementation
|
|
21
|
+
is pinned against **Veriff's own published mock vector**, and responses carry the documented
|
|
22
|
+
`X-AUTH-CLIENT` + `X-HMAC-SIGNATURE` sender headers, so a consumer that authenticates the sender
|
|
23
|
+
gets the real thing.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# serve the twin (writable by default; --read-only to reject writes with 405)
|
|
29
|
+
bun packages/twin/veriff/src/cli.ts serve --port 8123
|
|
30
|
+
|
|
31
|
+
# check the routing table against the handler (dev-only)
|
|
32
|
+
bun packages/twin/veriff/src/cli.ts conformance
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Point a client at it exactly as you would at Veriff — the reference consumer's shape:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const res = await fetch('http://127.0.0.1:8123/v1/sessions', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'X-AUTH-CLIENT': process.env.VERIFF_API_KEY!, 'Content-Type': 'application/json' },
|
|
41
|
+
body: JSON.stringify({ verification: { vendorData: partner.id, person: { firstName, lastName } } }),
|
|
42
|
+
});
|
|
43
|
+
// → 201 { status: 'success', verification: { id, url, host, status, sessionToken, endUserId, vendorData } }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The twin's default credentials are fixtures, not secrets: `VERIFF_API_KEY` =
|
|
47
|
+
`11111111-2222-4333-8444-555555555555` and `VERIFF_SHARED_SECRET` =
|
|
48
|
+
`abcdef12-abcd-abcd-abcd-abcdef012345` (the secret from Veriff's own published mock vector). Pass
|
|
49
|
+
`--shared-secret` / `createVeriffTwinServer({ apiKey, sharedSecret })` to use your own.
|
|
50
|
+
|
|
51
|
+
### Driving the decision lifecycle
|
|
52
|
+
|
|
53
|
+
Real Veriff decides a session with OCR, face matching, liveness and sometimes a human reviewer.
|
|
54
|
+
What the twin models faithfully is the
|
|
55
|
+
**lifecycle** those decisions move through, driven deterministically by the caller through a
|
|
56
|
+
**twin-only** control route (namespaced out of the vendor's surface, and deliberately absent from the
|
|
57
|
+
capability manifest — it is test scaffolding, not coverage):
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
POST /v1/sessions → created
|
|
61
|
+
PATCH /v1/sessions/{id} {status:'submitted'} → submitted (+ the 7002 event webhook)
|
|
62
|
+
POST /v1/_twin/sessions/{id}/decision → approved | declined | resubmission_requested
|
|
63
|
+
| review | expired | abandoned (+ the decision webhook)
|
|
64
|
+
POST /v1/_twin/sessions/{id}/event {started} → started (+ the 7001 event webhook)
|
|
65
|
+
GET /v1/_twin/deliveries[?sessionId=] → every webhook the twin would have PUSHED, signed
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Webhook deliveries are **recorded, signed exactly as they would go on the wire, and never put on a
|
|
69
|
+
socket** — so a consumer can read one back and run its real verification code against it offline,
|
|
70
|
+
with no receiver to stand up.
|
|
71
|
+
|
|
72
|
+
## Coverage
|
|
73
|
+
|
|
74
|
+
Coverage is measured against the **real Veriff surface** as the denominator (see
|
|
75
|
+
`src/veriff-capabilities.ts`) — it reads partial-but-honest and grows toward 100%. Run
|
|
76
|
+
`bun scripts/manifest-baseline-one.ts veriff` for the live numbers.
|
|
77
|
+
|
|
78
|
+
The denominator was enumerated top-down from `devdocs.veriff.com/llms.txt` (Veriff's own
|
|
79
|
+
machine-readable index of all 140 doc pages, whose `/apidocs/*` pages embed the real OpenAPI 3.0.0
|
|
80
|
+
documents) — the 18 documented Public API v1 operations, the three Feedback/Fraud API operations, the
|
|
81
|
+
sync-api registry operation, and the per-solution pages (Document+Selfie, Document-only, Biometric
|
|
82
|
+
Authentication/Liveness, Selfie2Selfie, Age Estimation, Unstructured Docs, Proof of Address, AML
|
|
83
|
+
screening, NFC/ePassport, UK DIATF, and the nine database verifications).
|
|
84
|
+
|
|
85
|
+
**Modeled (done, with failable offline verifies):**
|
|
86
|
+
|
|
87
|
+
- **Sessions** — create (`201`, all seven required `verification` fields, `url = host + '/v/' +
|
|
88
|
+
sessionToken`), `PATCH … {status:'submitted'}` (the only value the vendor accepts), delete;
|
|
89
|
+
the documented negative paths: missing `verification` (`400`/`1101`), non-string / over-1,000-char
|
|
90
|
+
`vendorData` (`1501` / `1500`), non-HTTPS `callback` (`1302`), re-submit (`Session has already been
|
|
91
|
+
submitted`), delete-while-submitted (`1306` `Session in progress.`) and delete-in-review (`1305`
|
|
92
|
+
`Session is not in a completed status.`).
|
|
93
|
+
- **Decisions** — `verification: null` before a decision (including while `submitted`), then
|
|
94
|
+
approved `9001` / declined `9102` + granular reason & reasonCode / resubmission `9103` (which
|
|
95
|
+
re-opens the session for capture) / expired `9104` / abandoned `9121` / review with a **null**
|
|
96
|
+
code (Veriff publishes none — see below); extracted
|
|
97
|
+
`person` + `document` kept distinct from the hints supplied at creation; `riskLabels`; the
|
|
98
|
+
state-machine guard (`1304`) in both directions — no jump straight from `created`, and no second
|
|
99
|
+
decision over a terminal one.
|
|
100
|
+
- **Attempts** — the `verifications[]` array with `id`/`status`/`userDefinedData`/`createdTime`,
|
|
101
|
+
reverse-chronological across a resubmission cycle, empty (not `404`) before the first attempt.
|
|
102
|
+
- **Person** — `GET /v1/sessions/{id}/person`, `null` until a decision exists.
|
|
103
|
+
- **Media** — upload (`200` — the media endpoint declares exactly one success response and it is
|
|
104
|
+
not 201; the status-code table's 201 row is scoped to *session* creation — with the documented
|
|
105
|
+
image object; `name` mirrors `context`; `timestamp` is
|
|
106
|
+
deprecated and always `null`; `size` is the *decoded* byte count), context-enum (`1402`) and
|
|
107
|
+
base64 (`1401`) rejection, the `409` on upload-after-submit, per-session and per-attempt listing
|
|
108
|
+
(`images`/`videos`/`nfcDocuments`), and download as **real bytes under the object's own mimetype**.
|
|
109
|
+
- **Auth** — missing `X-AUTH-CLIENT` (`401`, the vendor's verbatim message); missing/wrong
|
|
110
|
+
`X-HMAC-SIGNATURE` (`401`/`1812`); the `POST /v1/sessions` signing exemption *and* that every other
|
|
111
|
+
endpoint still requires one; body-vs-path-resource-id signing; the published mock vector; and the
|
|
112
|
+
documented response sender headers.
|
|
113
|
+
- **Webhooks** — the decision delivery carrying byte-identical content to the decision endpoint; a
|
|
114
|
+
real consumer's verification passing over the raw body; rejection of a tampered body, a wrong
|
|
115
|
+
`x-auth-client`, a missing signature and a wrong secret — each by its own reason; the 7002
|
|
116
|
+
`submitted` and 7001 `started` event payloads (no `verification` key, which is the discriminator a
|
|
117
|
+
real receiver routes on); and no delivery at all when no callback was configured.
|
|
118
|
+
- **Errors + platform** — the `{status:'fail', code, message}` envelope with `code` as a **string**
|
|
119
|
+
(the schema's declared type, despite every value looking numeric); unmodeled routes failing like
|
|
120
|
+
the vendor (`404`); `readOnly` refusing every write with `405`; and two deliberate **dirty-state**
|
|
121
|
+
verifies (ids never reused across delete→recreate; a decision after a resubmission cycle reports
|
|
122
|
+
the *latest* attempt).
|
|
123
|
+
- **Connector** — pull decisions/attempts/media over an injected client, idempotent re-pull
|
|
124
|
+
(`deltasAppended` → 0), no id collision in **either** direction (local mint after a pull, pull
|
|
125
|
+
after a local mint), push create/submit/delete with confirmation **only** on `status:'success'`,
|
|
126
|
+
the pure action→request and signing-payload rules, mappers that rename the kernel's reserved META
|
|
127
|
+
keys, and the client-side rate budget refusing past the ceiling with the injected fake's call count
|
|
128
|
+
**unchanged**.
|
|
129
|
+
|
|
130
|
+
A **fidelity test** (`src/veriff-fetch.integration.test.ts`) drives the twin over **real-transport
|
|
131
|
+
`fetch`** with a client rebuilt from the reference consumer's own source — same headers, same signing
|
|
132
|
+
payload, same shape checks. Veriff publishes **no server-side SDK** (its npm org ships browser and
|
|
133
|
+
mobile packages only), so a real server integration hand-rolls exactly this, and
|
|
134
|
+
[Adding a twin](../../../docs/contributing/adding-a-twin.md) §10 sanctions it for precisely this case. The same
|
|
135
|
+
file additionally pins **`@veriff/js-sdk` v2.0.0's wire contract**, read from the published bundle.
|
|
136
|
+
|
|
137
|
+
**`todo` (real surface enumerated but not yet modeled):** watchlist screening / AML (`GET`+`PATCH`,
|
|
138
|
+
hits, `202` in-progress, `402` not-enabled, its webhook family, `pepSanctionMatch`); collected-data
|
|
139
|
+
/ device intelligence and `technicalData`; bulk face import; `validate-registry` (both the Public API
|
|
140
|
+
and the synchronous `sync-api.veriff.me` variant) and the three legacy registry decisions; the ten
|
|
141
|
+
database verifications; the ten IDV solutions and their decision-payload objects (`riskScore`,
|
|
142
|
+
`additionalVerifiedData`, `biometricAuthentication`, `udocs`, `comments`, `submissionTime`, `tag`);
|
|
143
|
+
the full granular decline/resubmission reason-code tables; the complete `person`/`document` field
|
|
144
|
+
sets; Proof of Address (validation, matching, fraud check); PDF/NFC/video media and the documented
|
|
145
|
+
size limits; the web-flow event actions 7007-7011 and the `context` object; the user-defined-statuses,
|
|
146
|
+
Full Auto and registry webhook families; delivery retries; multiple shared secrets with a master
|
|
147
|
+
signing key; the Feedback/Fraud API on its own host with the `VRF-*` header scheme; 7-day session
|
|
148
|
+
expiry; the 9-resubmission cap; the served `429`; the precise credential (1801-1819) and
|
|
149
|
+
troubleshooting (1001-2104) code taxonomies; and five connector gaps (media bytes, watchlist, person,
|
|
150
|
+
media push, mid-batch back-off) filed in `pull-audit.json`.
|
|
151
|
+
|
|
152
|
+
### The twin refuses to invent a `GET /v1/sessions/{id}`
|
|
153
|
+
|
|
154
|
+
Veriff **has no such endpoint** — its API reference index lists only `POST`/`PATCH`/`DELETE` on that
|
|
155
|
+
path, and GETs on the sub-resources (`/decision`, `/person`, `/attempts`, `/media`,
|
|
156
|
+
`/watchlist-screening`). An integration learns a session's state from the webhooks and the decision
|
|
157
|
+
endpoint, which is exactly why the reference consumer stores its own `veriffSessionId` and status.
|
|
158
|
+
Serving a convenient read here would be the mirror image of the "unmodeled ops fail like the vendor"
|
|
159
|
+
rule — an *invented* op — so a `GET` on that path answers the vendor's `404`, and
|
|
160
|
+
`veriff.sessions.no_get_endpoint` pins it while asserting the sub-resources still answer for the
|
|
161
|
+
same live session. Verifies that need to read a session's status back use the twin-only
|
|
162
|
+
`GET /v1/_twin/sessions/{id}`, which is scaffolding and is not in the manifest.
|
|
163
|
+
|
|
164
|
+
### Where Veriff's own docs contradict themselves
|
|
165
|
+
|
|
166
|
+
Recorded rather than papered over, because a twin that silently picks a side teaches the wrong thing:
|
|
167
|
+
|
|
168
|
+
1. **`POST /v1/sessions` — 200 or 201?** The embedded OpenAPI declares the success response `200`;
|
|
169
|
+
the vendor's own HTTP-status-codes table says `201` ("returned when session creation was a
|
|
170
|
+
success"); and `@veriff/js-sdk` v2.0.0 hard-checks `201 === xhr.status`. **This twin answers 201**,
|
|
171
|
+
because answering 200 would break the shipped SDK.
|
|
172
|
+
2. **`review` has no published code.** The decision endpoint's `code` enum is exactly
|
|
173
|
+
`[9001, 9102, 9103, 9104, 9121]` and its `status` enum is
|
|
174
|
+
`[approved, declined, resubmission_requested, expired, abandoned]` — yet the decision **webhook**
|
|
175
|
+
page lists `review` among the statuses it sends (it is opt-in: "Only if previously agreed with
|
|
176
|
+
Veriff"). So `review` is a real status with no number of its own. **This twin emits `code: null`
|
|
177
|
+
for it** rather than borrowing 9121, which belongs to `abandoned` (the endpoint's prose says so,
|
|
178
|
+
and its `session_abandoned_generic` example pairs them explicitly, as
|
|
179
|
+
`session_expired_generic` pairs `expired` with 9104). Borrowing would tell a consumer branching
|
|
180
|
+
on `code` that a manual-review case was abandoned. `veriff.decisions.review_code` and
|
|
181
|
+
`veriff.decisions.code_numbering` are the filed todos. Note the reference consumer keys off
|
|
182
|
+
`status`, never `code` — which is the behaviour a twin should encourage.
|
|
183
|
+
|
|
184
|
+
Two smaller ones, both recorded in the code where they bite. The DELETE endpoint's prose lists seven
|
|
185
|
+
deletable statuses (`created`, `started`, `approved`, `declined`, `resubmission_requested`, `expired`,
|
|
186
|
+
`abandoned`) while its own `1305` example says the session "must be in a completed state (`approved`,
|
|
187
|
+
`declined`, `expired`, `abandoned`)" — the twin follows the prose list, because the same page also
|
|
188
|
+
describes a decision webhook firing when a `created`/`started`/`resubmission_requested` session is
|
|
189
|
+
deleted, which only makes sense if those are deletable. And the attempts endpoint's published `status`
|
|
190
|
+
enum omits `approved` while its own examples show it — the twin follows the examples
|
|
191
|
+
(`veriff.attempts.status_enum` is filed to pin it). The decision schema declares `code` and
|
|
192
|
+
`reasonCode` as integers and the decision *webhook* sample agrees (`"code": 9001`), while the
|
|
193
|
+
decision *endpoint*'s own examples quote them (`"code": "9121"`) — the twin emits numbers and files
|
|
194
|
+
`veriff.decisions.code_wire_type`. And the DELETE page publishes both a blanket
|
|
195
|
+
`1305` refusal for every non-deletable status *and* a `1306` "Session in progress." whose stated
|
|
196
|
+
trigger (`started`) is on the deletable list — so routing `submitted` → `1306` is this twin's
|
|
197
|
+
disambiguation, said plainly as such in the code and filed as `veriff.sessions.delete_refusal_codes`.
|
|
198
|
+
|
|
199
|
+
A third: the HMAC doc's Python sample emits a `sha256=` prefix that contradicts the same
|
|
200
|
+
page's mock vector and every other sample on it. The twin follows the vector — bare lowercase hex.
|
|
201
|
+
|
|
202
|
+
### No UI mirror
|
|
203
|
+
|
|
204
|
+
**The API is the product here.** When someone does Veriff's core job *the way the party integrating
|
|
205
|
+
it does* — POST a session, embed the hosted flow, receive the decision webhook, read the decision —
|
|
206
|
+
they write code. The only screen in that loop is Veriff's **own hosted capture flow** (camera,
|
|
207
|
+
document framing, retries, localisation) served from the session `url`, which is Veriff's first-party
|
|
208
|
+
product surface rather than a customer-operated console. The twin returns a real session `url`, so
|
|
209
|
+
an integration's redirect/embed path is exercised end to end. Coverage is API + connector, and the
|
|
210
|
+
manifest has zero `ui` capabilities that claim a mirror.
|
|
211
|
+
|
|
212
|
+
The counter-argument is real and is recorded in `ui-scope.json` rather than dismissed: **Veriff
|
|
213
|
+
Station** *is* a dashboard where a compliance team reviews sessions, decisions and captured media,
|
|
214
|
+
and [Adding a twin](../../../docs/contributing/adding-a-twin.md) warns that a thin REST API is evidence *for* a
|
|
215
|
+
mirror, not against it. What settles it here is that the UI in question is the *end user's* one-time
|
|
216
|
+
capture flow rather than the customer's workspace — and that reproducing the review console would
|
|
217
|
+
mean rendering document photographs a local twin never has. The reference consumer agrees in
|
|
218
|
+
practice: dub embeds `@veriff/incontext-sdk` and then built its **own** admin identity-verification
|
|
219
|
+
screen rather than working in Veriff Station. Re-opening this is legitimate.
|
|
220
|
+
|
|
221
|
+
## Architecture
|
|
222
|
+
|
|
223
|
+
State uses the shared kernel; see [the model](../../../docs/concepts/the-model.md) for log,
|
|
224
|
+
checkpoint and branch semantics. There is no parallel mutable truth store.
|
|
225
|
+
There is **no vendor devDependency** — Veriff publishes no server-side SDK, so fidelity is proven
|
|
226
|
+
over real-transport `fetch`. The pack **`peerDepends`** on `@volter/world-core`, and conformance tooling
|
|
227
|
+
(`@volter/world-tooling`) is dev-only and imported lazily by the CLI.
|
|
228
|
+
|
|
229
|
+
Ids are **UUID v4**, because that is what Veriff's ids are (`"format": "uuid"` on every id in the
|
|
230
|
+
published schemas) — and because entropy, never a row count, is what makes a local mint and a pulled
|
|
231
|
+
vendor id incapable of colliding in either direction. Veriff's payloads are full of `id` fields, and
|
|
232
|
+
the kernel's projection RESERVES `type`/`id`/`updatedAt` as META (a resource field with one of those
|
|
233
|
+
names is silently dropped), so every vendor id is stored under a renamed key (`sessionId`,
|
|
234
|
+
`attemptId`, `mediaId`) and mapped back at the boundary — on the local-mint path and the connector's
|
|
235
|
+
pull path alike.
|
|
236
|
+
|
|
237
|
+
**Pull is handle-driven, and that is the vendor's doing:** Veriff publishes no account-wide list
|
|
238
|
+
endpoint — there is no `GET /v1/sessions`, and every documented read is scoped to an id the caller
|
|
239
|
+
already holds — so `syncVeriffFromReal` takes the session ids to pull, exactly as a real integration
|
|
240
|
+
does. `pull-audit.json` records this rather than filing an enumeration gap for surface the API does
|
|
241
|
+
not expose.
|
|
242
|
+
|
|
243
|
+
Every live call goes through **one guarded client** (`liveVeriffExecute`) with a persistent,
|
|
244
|
+
token-keyed spend ledger that throws instead of calling past the ceiling — see `src/veriff-budget.ts`
|
|
245
|
+
for how Veriff's two published limits (600/min enterprise, 30/min self-serve session creation;
|
|
246
|
+
10-per-24h + 5-per-1h on delete) were turned into numbers, and for what the guard honestly does not
|
|
247
|
+
enforce.
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { keepProcessAlive } from '@volter/world-core/lifecycle';
|
|
3
|
+
// world-veriff CLI: serve the Veriff Station API twin, or run conformance.
|
|
4
|
+
//
|
|
5
|
+
// No `mirror` command: Veriff is an API-first vendor (the integrator's work is code — create a
|
|
6
|
+
// session, embed the hosted flow, receive the decision webhook), so this pack ships no React
|
|
7
|
+
// mirror. See README.md `## Coverage` → `### No UI mirror`.
|
|
8
|
+
import { hasFlag, optionValue } from '@volter/world-core/args';
|
|
9
|
+
import { createVeriffTwinServer } from "./veriff-server.js";
|
|
10
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
11
|
+
const port = Number(optionValue(rest, '--port', '0')) || undefined;
|
|
12
|
+
const root = optionValue(rest, '--root') || undefined;
|
|
13
|
+
const sharedSecret = optionValue(rest, '--shared-secret') || undefined;
|
|
14
|
+
const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
|
|
15
|
+
if (cmd === 'serve') {
|
|
16
|
+
const s = await createVeriffTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}), ...(sharedSecret ? { sharedSecret } : {}) });
|
|
17
|
+
process.stdout.write(`veriff twin (Station API v1)${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:${s.port}\n`);
|
|
18
|
+
await keepProcessAlive();
|
|
19
|
+
}
|
|
20
|
+
else if (cmd === 'conformance') {
|
|
21
|
+
// dev-only; lazy so the bin runs without @volter/world-tooling in the runtime graph
|
|
22
|
+
const { checkVeriffConformance } = await import("./veriff-conformance.js");
|
|
23
|
+
const report = await checkVeriffConformance({ ...(root ? { root } : {}) });
|
|
24
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
25
|
+
if (!report.ok)
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
process.stdout.write('Usage: world-veriff serve|conformance [--port N] [--root DIR] [--shared-secret S] [--read-only]\n');
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { handleVeriffTwinRequest, TWIN_API_KEY, TWIN_SHARED_SECRET, VERIFF_DECISION_CODES, VERIFF_DECISION_STATUSES, VERIFF_ERROR_CODES, VERIFF_ERROR_MESSAGES, VERIFF_EVENT_CODES, VERIFF_IMPLEMENTED_ENDPOINTS, VERIFF_RESOURCE_TYPES, VERIFF_SESSION_STATUSES, } from './veriff-twin.js';
|
|
2
|
+
export type { VeriffRequest, VeriffResponse, VeriffSessionStatus, VeriffDecisionStatus } from './veriff-twin.js';
|
|
3
|
+
export { createVeriffTwinFetch, createVeriffTwinServer, veriffResponseHeaders, type VeriffTwinFetchOptions } from './veriff-server.js';
|
|
4
|
+
export { AUTH_CLIENT_HEADER, HMAC_SIGNATURE_HEADER, signaturesMatch, veriffSignature, veriffSignatureBytes, verifyVeriffSignature, } from './veriff-signature.js';
|
|
5
|
+
export { buildSignedDelivery, emitVeriffWebhook, VERIFF_EVENT_ACTIONS, verifyWebhook, VeriffWebhookVerificationError, } from './veriff-events.js';
|
|
6
|
+
export type { VeriffDecisionPayload, VeriffEventAction, VeriffEventPayload, VeriffWebhookDelivery, VeriffWebhookPayload } from './veriff-events.js';
|
|
7
|
+
export { liveVeriffExecute, mapSessionDecision, mapSessionAttempt, mapSessionMedia, pullVeriffAttempts, pullVeriffDecisions, pullVeriffMedia, pushPendingVeriffActions, signaturePayloadFor, syncVeriffFromReal, veriffRequestForAction, } from './veriff-connector.js';
|
|
8
|
+
export type { LiveVeriffOptions, VeriffExecute } from './veriff-connector.js';
|
|
9
|
+
export { VERIFF_BUDGET_CEILING, VERIFF_BUDGET_MAX_RETRY_AFTER_S, VERIFF_BUDGET_WINDOW_MS, VERIFF_CALL_WEIGHTS, VERIFF_RATE_BUDGET, VeriffBudget, VeriffBudgetError, veriffBudgetPath, veriffCallWeight, } from './veriff-budget.js';
|
|
10
|
+
export type { VeriffBudgetErrorKind, VeriffBudgetOptions, VeriffBudgetReservation, VeriffBudgetSnapshot } from './veriff-budget.js';
|
|
11
|
+
import { type TwinPack } from '@volter/world-core';
|
|
12
|
+
export declare const pack: TwinPack;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// @volter/twin-veriff — the Veriff identity-verification twin (one vendor, one package), built on
|
|
2
|
+
// the shared @volter/world-core kernel. REST transport over Veriff's Station API
|
|
3
|
+
// (`https://stationapi.veriff.com/v1`), a stateful session→attempt→decision lifecycle, REAL
|
|
4
|
+
// `X-HMAC-SIGNATURE` request signing and HMAC-signed decision/event webhooks.
|
|
5
|
+
//
|
|
6
|
+
// Veriff publishes NO server-side SDK — a real integration hand-rolls a `fetch` client (the
|
|
7
|
+
// reference consumer, dub, does exactly that in `apps/web/lib/veriff/client.ts`) — so this pack
|
|
8
|
+
// declares no vendor devDependency and proves fidelity over real-transport `fetch` instead.
|
|
9
|
+
//
|
|
10
|
+
// NO React mirror: Veriff is an API-first vendor for the party that integrates it (create a
|
|
11
|
+
// session, embed the hosted flow, receive the decision webhook, read the decision). See
|
|
12
|
+
// README.md `## Coverage` → `### No UI mirror`. Coverage = API + connector.
|
|
13
|
+
// (Conformance/capability tooling lives in @volter/world-tooling, a dev dependency.)
|
|
14
|
+
export { handleVeriffTwinRequest, TWIN_API_KEY, TWIN_SHARED_SECRET, VERIFF_DECISION_CODES, VERIFF_DECISION_STATUSES, VERIFF_ERROR_CODES, VERIFF_ERROR_MESSAGES, VERIFF_EVENT_CODES, VERIFF_IMPLEMENTED_ENDPOINTS, VERIFF_RESOURCE_TYPES, VERIFF_SESSION_STATUSES, } from "./veriff-twin.js";
|
|
15
|
+
export { createVeriffTwinFetch, createVeriffTwinServer, veriffResponseHeaders } from "./veriff-server.js";
|
|
16
|
+
export { AUTH_CLIENT_HEADER, HMAC_SIGNATURE_HEADER, signaturesMatch, veriffSignature, veriffSignatureBytes, verifyVeriffSignature, } from "./veriff-signature.js";
|
|
17
|
+
export { buildSignedDelivery, emitVeriffWebhook, VERIFF_EVENT_ACTIONS, verifyWebhook, VeriffWebhookVerificationError, } from "./veriff-events.js";
|
|
18
|
+
export { liveVeriffExecute, mapSessionDecision, mapSessionAttempt, mapSessionMedia, pullVeriffAttempts, pullVeriffDecisions, pullVeriffMedia, pushPendingVeriffActions, signaturePayloadFor, syncVeriffFromReal, veriffRequestForAction, } from "./veriff-connector.js";
|
|
19
|
+
// The client-side rate budget — the fail-closed backstop `liveVeriffExecute` routes every live
|
|
20
|
+
// request through. The MECHANISM is the kernel's shared, vendor-agnostic `RateBudget`; what lives
|
|
21
|
+
// here is Veriff's DECLARATION (window/ceiling/per-endpoint weights) plus the vendor-bound
|
|
22
|
+
// bindings. Exported so an operator can inspect spend (`snapshot`) and so a caller can catch
|
|
23
|
+
// `VeriffBudgetError` by type; there is deliberately no export that disables the guard.
|
|
24
|
+
export { VERIFF_BUDGET_CEILING, VERIFF_BUDGET_MAX_RETRY_AFTER_S, VERIFF_BUDGET_WINDOW_MS, VERIFF_CALL_WEIGHTS, VERIFF_RATE_BUDGET, VeriffBudget, VeriffBudgetError, veriffBudgetPath, veriffCallWeight, } from "./veriff-budget.js";
|
|
25
|
+
// Registry descriptor: the pack self-describes so tooling can discover it.
|
|
26
|
+
import { registerPack } from '@volter/world-core';
|
|
27
|
+
import { signaturePayloadFor } from "./veriff-connector.js";
|
|
28
|
+
import { HMAC_SIGNATURE_HEADER } from "./veriff-signature.js";
|
|
29
|
+
import { performVeriffAction, syncVeriffFromRemote } from "./veriff-connector.js";
|
|
30
|
+
import { VERIFF_RATE_BUDGET as RATE_BUDGET } from "./veriff-budget.js";
|
|
31
|
+
export const pack = {
|
|
32
|
+
// PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of
|
|
33
|
+
// the real state system. Moved 2026-09-08.
|
|
34
|
+
protocol: '2',
|
|
35
|
+
// Veriff DOES publish webhooks (the decision callback is the whole integration), so a world that
|
|
36
|
+
// wires them hears about a decision as it lands; the poll is the backstop for one that has not.
|
|
37
|
+
refresh: { every: '5m', webhook: true, onDemand: { atMost: '30s' } },
|
|
38
|
+
stateSystem: { perform: performVeriffAction, refresh: syncVeriffFromRemote },
|
|
39
|
+
// The round trip is a session create — Veriff's own write, and the vendor mints a fresh id for
|
|
40
|
+
// every one, so a second send on a branch is a second session rather than a collision.
|
|
41
|
+
roundTrip: { method: 'POST', path: '/v1/sessions', body: { verification: { vendorData: 'round-trip' } }, headers: { 'x-auth-client': 'round-trip' } },
|
|
42
|
+
// Veriff signs EVERY call but one: `X-HMAC-SIGNATURE` over the request body for a write, and over
|
|
43
|
+
// the resource id in the path for a GET/DELETE. `signaturePayloadFor` is the pack's own pure
|
|
44
|
+
// statement of which bytes those are — it takes no secret, so the secret stays in the kernel
|
|
45
|
+
// (docs/concepts/the-model.md#the-rules, rule 5, "the secret never crossing into pack code").
|
|
46
|
+
//
|
|
47
|
+
// TWO THINGS THIS LINE HAS TO GET RIGHT, both measured against the pack's live executor:
|
|
48
|
+
// • `POST /sessions` is the one endpoint the vendor EXEMPTS from signing — there is no session
|
|
49
|
+
// to sign for yet — so the canonical answers `null` and the request goes out unsigned.
|
|
50
|
+
// • `signaturePayloadFor` reads the resource id POSITIONALLY out of a path with no version
|
|
51
|
+
// prefix (`/sessions/{id}` → the id). The kernel sends `/v1/...`, so the prefix comes off
|
|
52
|
+
// first; signing `sessions` instead of the session id would verify against nothing.
|
|
53
|
+
auth: {
|
|
54
|
+
in: 'signature',
|
|
55
|
+
algorithm: 'hmac-sha256',
|
|
56
|
+
encoding: 'hex',
|
|
57
|
+
header: HMAC_SIGNATURE_HEADER,
|
|
58
|
+
canonical: ({ method, path, body }) => {
|
|
59
|
+
const bare = path.replace(/^\/v1(?=\/|$)/, '');
|
|
60
|
+
if (method === 'POST' && bare === '/sessions')
|
|
61
|
+
return null;
|
|
62
|
+
return signaturePayloadFor(method, bare, body);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
parityOrigin: 'http://twin',
|
|
66
|
+
vendor: 'veriff',
|
|
67
|
+
// The SAME object veriff-budget.ts declares at module load — one source of truth, so registering
|
|
68
|
+
// the pack and importing the connector can never arm two different ceilings.
|
|
69
|
+
rateBudget: RATE_BUDGET,
|
|
70
|
+
transport: 'rest',
|
|
71
|
+
archetype: 'crud',
|
|
72
|
+
bin: 'world-veriff',
|
|
73
|
+
resources: ['session', 'attempt', 'media', 'delivery'],
|
|
74
|
+
specSource: 'docs.veriff.com / devdocs.veriff.com (hand-authored from the published Station API reference), cross-checked against the real consumer dub (apps/web/lib/veriff/*, app/api/veriff/webhook/*)',
|
|
75
|
+
description: 'Veriff Station API twin — sessions, the created→submitted→approved/declined decision lifecycle, HMAC-signed request auth and decision/event webhooks, attempts and media. Watchlist/AML screening is a filed todo area, deliberately unmodeled.',
|
|
76
|
+
// Adoption + interception, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
|
|
77
|
+
// 2026-08-31).
|
|
78
|
+
//
|
|
79
|
+
// Veriff publishes NO server-side SDK — a real integration hand-rolls a `fetch` client against
|
|
80
|
+
// the Station API (the reference consumer, dub, does exactly that in
|
|
81
|
+
// apps/web/lib/veriff/client.ts) — so the only npm package that is genuinely a CLIENT OF THE
|
|
82
|
+
// MODELED SURFACE is `@veriff/js-sdk`: v2.0.0's `createSession` XHRs `POST {host}/v1/sessions`
|
|
83
|
+
// straight from the browser with `x-auth-client`, and its `host` is a constructor option, so
|
|
84
|
+
// pointing it at a twin is configuration. Its siblings are covered by the `@veriff/` SCOPE
|
|
85
|
+
// rather than listed as sdks, because they are NOT API clients: `@veriff/incontext-sdk` makes
|
|
86
|
+
// ZERO network calls of its own (verified against the published v2.5.0 bundle: no fetch, no
|
|
87
|
+
// XMLHttpRequest, no hardcoded URL) — it iframes the session `url` the API already returned and
|
|
88
|
+
// listens for postMessage — and the react-native/Cordova packages launch the native capture
|
|
89
|
+
// flow. Listing them as sdks would claim the twin intercepts traffic they never send; leaving
|
|
90
|
+
// them out of the scope entirely would report the exact package the reference consumer installs
|
|
91
|
+
// as unknown-sdk.
|
|
92
|
+
//
|
|
93
|
+
// VERIFF_API_KEY is the credential that stems here — the name the reference consumer uses
|
|
94
|
+
// verbatim (dub's apps/web/.env.example). Its sibling VERIFF_SHARED_SECRET deliberately does
|
|
95
|
+
// NOT: it stems to `veriffshared` under the broad suffix list (`_SECRET` splits before
|
|
96
|
+
// `SHARED`), and it matches no STRICT suffix at all, so it never reaches the lookup and cannot
|
|
97
|
+
// raise a false unknown-vendor alarm either.
|
|
98
|
+
adoption: {
|
|
99
|
+
// Veriff ships no Python SDK - its clients are the JS `@veriff/js-sdk` and the raw REST API.
|
|
100
|
+
pypi: [],
|
|
101
|
+
sdks: ['@veriff/js-sdk'], scopes: ['@veriff/'], envStems: ['VERIFF'],
|
|
102
|
+
},
|
|
103
|
+
// The API URL is ACCOUNT-SPECIFIC by design — Veriff's reference tells you to read your BaseURL
|
|
104
|
+
// off the Customer Portal and its OpenAPI `servers` entry is the placeholder
|
|
105
|
+
// `https://example-base-url` — so BOTH hosts that appear in official material are routed: the
|
|
106
|
+
// reference consumer and the media code samples use `stationapi.veriff.com`, while
|
|
107
|
+
// `@veriff/js-sdk` v2.0.0 compiles in `api.veriff.me` as its default. Deliberately EXACT
|
|
108
|
+
// matches, not a `.veriff.com`/`.veriff.me` suffix: `magic.veriff.me` and `alchemy.veriff.com`
|
|
109
|
+
// serve the HOSTED end-user capture flow (Veriff's own first-party web app; the twin returns a
|
|
110
|
+
// real session `url` rather than rendering it), `cdn.veriff.me` serves the browser SDK bundles,
|
|
111
|
+
// and `feedback.api.veriff.com` is the separate Fraud API with its own VRF-* header scheme that
|
|
112
|
+
// this pack does not model. Routing any of those into the twin would intercept requests it
|
|
113
|
+
// cannot answer.
|
|
114
|
+
hosts: [{ host: 'stationapi.veriff.com' }, { host: 'api.veriff.me' }],
|
|
115
|
+
// A Veriff client calls stationapi.veriff.com/v1/… — the dev proxy forwards '/v1/' to the twin
|
|
116
|
+
// and strips the absolute host so calls come back same-origin.
|
|
117
|
+
browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://stationapi.veriff.com' },
|
|
118
|
+
};
|
|
119
|
+
registerPack(pack);
|