@hubbyesim/sdk 2.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/CHANGELOG.md +79 -0
- package/LICENSE +21 -0
- package/README.md +346 -0
- package/dist/index.cjs +1656 -0
- package/dist/index.d.cts +1877 -0
- package/dist/index.d.ts +1877 -0
- package/dist/index.js +1622 -0
- package/llms.txt +80 -0
- package/package.json +72 -0
- package/signing-vectors.json +87 -0
package/llms.txt
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @hubbyesim/sdk
|
|
2
|
+
|
|
3
|
+
> Official TypeScript SDK for the Hubby Partner API v1. Use it on your server to sell Hubby eSIMs in your own booking flow or app: list the catalogue, book, issue the eSIM, show the install screen, terminate, and read status and usage. It signs every request with the Partner API HMAC scheme, adds an Idempotency-Key to every POST and reuses it on retries, retries 429 answers and network errors, verifies webhook signatures, and gives one `Hubby` class with a named method per Partner API operation. Requires Node.js 24 or later, or any runtime with fetch and Web Crypto. No runtime dependencies. Never send the API secret to a browser or a mobile app.
|
|
4
|
+
|
|
5
|
+
Install:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @hubbyesim/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Primary entry point: `import { Hubby } from '@hubbyesim/sdk'`. Build `new Hubby({ apiKey, apiSecret })` once (production by default; `baseUrl: 'https://core-staging.hubbyesim.com/api'` for staging) and call its methods. A method takes path identifiers first, then one object with the request fields, then optional `{ signal, headers }`. It resolves with the response body and rejects with `HubbyApiError` (`status`, `code`, `message`, `errors`, `body`, `response`) on a non-2xx answer.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Hubby, HubbyApiError } from '@hubbyesim/sdk';
|
|
15
|
+
|
|
16
|
+
const hubby = new Hubby({
|
|
17
|
+
apiKey: process.env.HUBBY_API_KEY!,
|
|
18
|
+
apiSecret: process.env.HUBBY_API_SECRET!,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const catalogue = await hubby.getBundles();
|
|
22
|
+
const booked = await hubby.createBooking({
|
|
23
|
+
external_reference: 'ORDER-4471',
|
|
24
|
+
depart_at: '2026-11-02T00:00:00Z',
|
|
25
|
+
return_at: '2026-11-09T00:00:00Z',
|
|
26
|
+
locale: 'en',
|
|
27
|
+
package_specifications: [{ bundle_id: '<bundle id>', external_user_id: 'user-88213' }],
|
|
28
|
+
// Or by dimensions, which books the closest bundle: { destination: 'ES', package_type: 'data-limited', size: '3GB' }. The type is also accepted as 'data_limited', the spelling answers use.
|
|
29
|
+
});
|
|
30
|
+
const item = booked.data.items![0];
|
|
31
|
+
|
|
32
|
+
// Create the key once per issue attempt and store it with your own purchase record before the call.
|
|
33
|
+
// Do not derive it from the item id: after a terminate, the next issue needs a new key.
|
|
34
|
+
const idempotencyKey = crypto.randomUUID();
|
|
35
|
+
const issued = await hubby.issuePackage('ORDER-4471', item.id, {
|
|
36
|
+
idempotencyKey,
|
|
37
|
+
external_user_id: 'user-88213',
|
|
38
|
+
});
|
|
39
|
+
// A key replays its first answer for 24 hours (Idempotent-Replayed: true); after a terminate it answers 409 idempotency_key_spent: issue again with a new key.
|
|
40
|
+
// state 'redeemed': show issued.data.esim.install_links, or issued.data.esim.lpa as a QR code.
|
|
41
|
+
// issued.data (also getPackage): id (the id in getEsim's packages), state, bundle_id, issued_at, installed_at, activated_at, expires_at, terminated_at, esim { iccid, installed_at, install_links { ios, android }, lpa, smdp_address, matching_id }.
|
|
42
|
+
|
|
43
|
+
await hubby.terminatePackage('ORDER-4471', item.id, { reason: 'changed_plans' }); // resolves undefined (204)
|
|
44
|
+
const esim = await hubby.getEsim(issued.data.esim.iccid, { refresh: true });
|
|
45
|
+
// issued.data and each of esim.data.packages carry valid_until: when the data stops working, null until activation. expires_at is only the latest moment the package can still be activated.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Shop: list destinations with `getDestinations()` and the plans of one with `getDestinationBundles(destination)`. Do not load `getBundles()` per screen: it is the whole catalogue, for a periodic sync revalidated with its `ETag` (send it back as `If-None-Match` in the call's `headers`; an unchanged catalogue answers `304`, which rejects with `HubbyApiError` `status: 304`).
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// The answer is { countries, regions }, with no `data` around it.
|
|
52
|
+
// Each entry: id, name, type, iso2, iso3, iso3s, i18n_name; a region also has slug and country_names.
|
|
53
|
+
const { countries, regions } = await hubby.getDestinations();
|
|
54
|
+
// destination: a ULID, an ISO alpha-2 or alpha-3 code, or the English name; exact, any letter case. Anything else answers 404 not_found.
|
|
55
|
+
const plans = await hubby.getDestinationBundles('ES');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Add a package to a traveller's eSIM: each traveller has one Universal eSIM. Book again for the same `external_user_id`, then issue. The answer is `installed` once the eSIM is on the phone, so skip the install screen. There is no `topUp()` call; this is it. The Hubby apps offer a top-up only for a destination the traveller already holds, and any other destination is a purchase from the shop; the Partner API does not enforce this, so your app must. `getEsim` names only each package's `bundle_id` and `booking_id`: take a package's destination from its booking's item, or from your own record of what you sold.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const topUp = await hubby.createBooking({
|
|
62
|
+
external_reference: 'ORDER-5120',
|
|
63
|
+
package_specifications: [{ destination: 'ES', package_type: 'data-limited', size: '3GB', external_user_id: 'user-88213' }],
|
|
64
|
+
});
|
|
65
|
+
const added = await hubby.issuePackage('ORDER-5120', topUp.data.items![0].id, {
|
|
66
|
+
idempotencyKey: crypto.randomUUID(), // stored with your purchase record first
|
|
67
|
+
external_user_id: 'user-88213',
|
|
68
|
+
});
|
|
69
|
+
// added.data.state 'installed': the eSIM is already on the phone.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Docs
|
|
73
|
+
|
|
74
|
+
- [README](https://www.npmjs.com/package/@hubbyesim/sdk): client options, retries, the full book, issue, install, terminate and read flow, what to show per state, every method, errors, per-call options, webhook verification, signing without the client
|
|
75
|
+
- [Partner API guide](https://docs.hubbyesim.com/guides/native-api): every call, field, refusal code and webhook event
|
|
76
|
+
|
|
77
|
+
## Optional
|
|
78
|
+
|
|
79
|
+
- `CHANGELOG.md`, shipped in this package: what changed between versions
|
|
80
|
+
- Security contact: tech@hubbyesim.com (report a security problem there, not in a public issue)
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hubbyesim/sdk",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for the Hubby Partner API: signed requests, idempotent retries, webhook verification and typed operations.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"hubby",
|
|
7
|
+
"esim",
|
|
8
|
+
"e-sim",
|
|
9
|
+
"travel",
|
|
10
|
+
"partner-api",
|
|
11
|
+
"sdk",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"author": "Hubby",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/hubbyesim/core.git",
|
|
20
|
+
"directory": "packages/sdk"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/hubbyesim/core/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/hubbyesim/core/tree/main/packages/sdk#readme",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=24"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.cjs",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./signing-vectors.json": "./signing-vectors.json",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"signing-vectors.json",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"CHANGELOG.md",
|
|
51
|
+
"llms.txt"
|
|
52
|
+
],
|
|
53
|
+
"sideEffects": false,
|
|
54
|
+
"scripts": {
|
|
55
|
+
"generate": "openapi-ts",
|
|
56
|
+
"check:api-version": "node scripts/check-api-version.mjs openapi/partner.json",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"prepublishOnly": "npm run check:api-version && npm test && npm run build"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@hey-api/openapi-ts": "^0.99.0",
|
|
64
|
+
"@types/node": "^24.19.2",
|
|
65
|
+
"tsup": "^8.3.0",
|
|
66
|
+
"typescript": "^5.7.0",
|
|
67
|
+
"vitest": "^5.0.3"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Known-answer vectors for the Hubby Partner API signing schemes. Read by the @hubbyesim/sdk vitest suite and by core (tests/Unit/Auth/Hmac/V1SchemeTest.php, tests/Unit/Integrations/PartnerWebhook/WebhookSignerTest.php), so the two implementations cannot drift. requests: HUBBY-HMAC-SHA256-v1 X-Signature over the six-line canonical string. webhooks: x-hubby-signature, sha256= plus hex HMAC-SHA256 of {timestamp}.{raw body}. Never change an expected signature to make a test pass; a mismatch is a signing change that breaks every Partner.",
|
|
3
|
+
"requests": [
|
|
4
|
+
{
|
|
5
|
+
"name": "a GET with no body",
|
|
6
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
7
|
+
"method": "GET",
|
|
8
|
+
"url": "https://core.hubbyesim.com/api/v1/me",
|
|
9
|
+
"host": "core.hubbyesim.com",
|
|
10
|
+
"request_uri": "/api/v1/me",
|
|
11
|
+
"timestamp": "1760000000",
|
|
12
|
+
"body": "",
|
|
13
|
+
"signature": "56c1d5b088ca50d70300627eab84f0fdb58fbca7b40898ce3e75ce1ca7452f85"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "a GET with a query string",
|
|
17
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
18
|
+
"method": "GET",
|
|
19
|
+
"url": "https://core.hubbyesim.com/api/v1/bookings?cursor=eyJpZCI6MX0&per_page=10",
|
|
20
|
+
"host": "core.hubbyesim.com",
|
|
21
|
+
"request_uri": "/api/v1/bookings?cursor=eyJpZCI6MX0&per_page=10",
|
|
22
|
+
"timestamp": "1760000000",
|
|
23
|
+
"body": "",
|
|
24
|
+
"signature": "779fc32db2d38930c899fba3a6c3ccfdc834ad2c0be95c2c6b35a1177811cff3"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "a GET on an explicit port",
|
|
28
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
29
|
+
"method": "GET",
|
|
30
|
+
"url": "http://localhost:8000/api/v1/esims/8944000000000000001?refresh=true",
|
|
31
|
+
"host": "localhost:8000",
|
|
32
|
+
"request_uri": "/api/v1/esims/8944000000000000001?refresh=true",
|
|
33
|
+
"timestamp": "1760000000",
|
|
34
|
+
"body": "",
|
|
35
|
+
"signature": "8c1f3948ffe666eab9c428d04fe13cd75f53a828cd5b12f2a5feb3b9c365466d"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "a POST with a JSON body",
|
|
39
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
40
|
+
"method": "POST",
|
|
41
|
+
"url": "https://core-staging.hubbyesim.com/api/v1/bookings",
|
|
42
|
+
"host": "core-staging.hubbyesim.com",
|
|
43
|
+
"request_uri": "/api/v1/bookings",
|
|
44
|
+
"timestamp": "1760000000",
|
|
45
|
+
"body": "{\"external_reference\":\"PARTNER-LOC-4471\",\"package_specifications\":[{\"bundle_id\":\"01J9X3K2M4N5P6Q7R8S9T0V1W2\",\"external_user_id\":\"partner-user-88213\"}]}",
|
|
46
|
+
"signature": "768634f85c6b28a93e15e2ca942c3c3b4d031e4caf3fd530aa188194ba430b69"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "a POST body with a newline and non-ASCII bytes",
|
|
50
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
51
|
+
"method": "POST",
|
|
52
|
+
"url": "https://core.hubbyesim.com/api/v1/bookings",
|
|
53
|
+
"host": "core.hubbyesim.com",
|
|
54
|
+
"request_uri": "/api/v1/bookings",
|
|
55
|
+
"timestamp": "1760000000",
|
|
56
|
+
"body": "{\n \"first_name\": \"Ana\",\n \"last_name\": \"García Ωmega\"\n}",
|
|
57
|
+
"signature": "75ffd5a57ff5d0a7b584824a146a7897f0fb5e0c4278431085bdfd3be77efbfe"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "a DELETE with a query string and no body",
|
|
61
|
+
"secret": "hubby-sdk-vector-secret-0f3c9a71",
|
|
62
|
+
"method": "DELETE",
|
|
63
|
+
"url": "https://core.hubbyesim.com/api/v1/bookings/PARTNER-LOC-4471/items/01J9X3K2M4N5P6Q7R8S9T0V1W2/package?reason=changed_plans",
|
|
64
|
+
"host": "core.hubbyesim.com",
|
|
65
|
+
"request_uri": "/api/v1/bookings/PARTNER-LOC-4471/items/01J9X3K2M4N5P6Q7R8S9T0V1W2/package?reason=changed_plans",
|
|
66
|
+
"timestamp": "1760000000",
|
|
67
|
+
"body": "",
|
|
68
|
+
"signature": "f2202fdc68fbfcced0041d9f9a8d6c75d4fc22639522f619aefde5fb153aa833"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"webhooks": [
|
|
72
|
+
{
|
|
73
|
+
"name": "a package.activated delivery",
|
|
74
|
+
"secret": "whsec_hubby_sdk_vector",
|
|
75
|
+
"timestamp": "1760000000",
|
|
76
|
+
"body": "{\"event\":\"package.activated\",\"timestamp\":\"2026-11-02T13:40:00.000Z\",\"data\":{\"iccid\":\"8944000000000000001\"},\"event_id\":\"package.activated:01J9X3K2M4N5P6Q7R8S9T0V1W2\",\"delivery_id\":\"01J9X3K2M4N5P6Q7R8S9T0V1W3\"}",
|
|
77
|
+
"signature": "sha256=c86bede0e3d359c66c57703b7ffb08e4fbe3f445b9bd7c028cc978c48f5a79a3"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "a body with non-ASCII bytes",
|
|
81
|
+
"secret": "whsec_hubby_sdk_vector",
|
|
82
|
+
"timestamp": "1760000000",
|
|
83
|
+
"body": "{\"event\":\"booking.created\",\"data\":{\"last_name\":\"García\"}}",
|
|
84
|
+
"signature": "sha256=55252e4ec2ad26b023e5a0a6b2334ae9e4cf777a4c4cf135526ed3b5d4113423"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|