@spritz-finance/api-client 0.5.0 → 0.7.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/README.md CHANGED
@@ -817,15 +817,23 @@ const accounts = await client.virtualAccounts.list()
817
817
 
818
818
  ## ACH Onramp (Direct Debit)
819
819
 
820
- ACH onramp lets users convert USD from their bank account into USDC delivered to a Solana wallet. The flow is:
820
+ ACH onramp lets users convert USD from their bank account into USDC delivered to a Solana wallet. The integration is a short server-side flow with one client-side Plaid step:
821
821
 
822
- 1. **Link bank account** via Plaid funding source created automatically
823
- 2. **Bind wallet** user signs a message proving wallet ownership
824
- 3. **Create deposit** user authorizes an ACH debit, USDC released to wallet
822
+ 1. **Server:** create a Plaid link token with `client.bankAccount.createLinkToken()`
823
+ 2. **Client:** run Plaid Link and send the public token/account IDs back to your server
824
+ 3. **Server:** complete linking with `client.bankAccount.completeLinking(...)`
825
+ 4. **Server:** find an active funding source and fetch limits with `client.fundingSource.getDepositLimits(id)`
826
+ 5. **Server:** prepare a quote with `client.deposit.prepare(...)`
827
+ 6. **Client:** show the quote and ACH authorization message to the user
828
+ 7. **Server:** create the deposit with `client.deposit.create(...)`; Spritz runs risk checks before initiating the ACH pull
829
+
830
+ Authorization is derived from the verified ACH funding source — no wallet signature is required.
831
+
832
+ If risk checks block the create step, the API returns 409 before any ACH debit is pulled. Prepare a new quote before retrying; blocked create attempts consume the original `preparationId`.
825
833
 
826
834
  For a complete walkthrough with code examples, request/response schemas, and deposit lifecycle documentation, see the **[ACH Onramp Integration Guide](docs/ach-onramp-guide.md)**.
827
835
 
828
- A standalone sandbox demo is available at `scripts/sandbox/ach-onramp.html` open it in a browser to walk through the full flow interactively.
836
+ A standalone sandbox demo is available at `scripts/sandbox/ach-onramp.html`. Run `yarn build && node scripts/sandbox/evidence-server.mjs`, then open `http://localhost:3001/ach-onramp.html` to test the SDK-backed flow and save redacted QC evidence.
829
837
 
830
838
  ## Sandbox
831
839
 
@@ -873,12 +881,31 @@ This endpoint returns 403 in production.
873
881
 
874
882
  - `capabilities.updated` — user capabilities changed
875
883
 
884
+ #### On-Ramp Events
885
+
886
+ - `onramp.created` — on-ramp record created after a deposit is authorized
887
+ - `onramp.updated` — on-ramp status, delivery, or reversal details updated
888
+ - `onramp.completed` — on-ramp delivery completed
889
+
890
+ #### ACH Debit Return Events
891
+
892
+ - `achDebitReturn.created` — ACH debit return recorded
893
+ - `achDebitReturn.updated` — ACH debit return details updated
894
+
895
+ Use `'*'` to subscribe a webhook endpoint to all current and future webhook events.
896
+
876
897
  ### Setup
877
898
 
878
899
  ```typescript
879
900
  const webhook = await client.webhook.create({
880
901
  url: 'https://my.webhook.url/spritz',
881
- events: ['account.created', 'account.updated', 'payment.completed'],
902
+ events: ['onramp.created', 'onramp.updated', 'achDebitReturn.created'],
903
+ })
904
+
905
+ // Subscribe to all events
906
+ await client.webhook.create({
907
+ url: 'https://my.webhook.url/spritz/all',
908
+ events: ['*'],
882
909
  })
883
910
  ```
884
911
 
@@ -898,13 +925,18 @@ Webhook payloads have the following shape:
898
925
  // List all webhooks
899
926
  const webhooks = await client.webhook.list()
900
927
 
928
+ // Update a webhook's event subscriptions
929
+ await client.webhook.update('webhook-id', {
930
+ events: ['onramp.updated', 'achDebitReturn.created', 'achDebitReturn.updated'],
931
+ })
932
+
901
933
  // Delete a webhook
902
934
  await client.webhook.delete('webhook-id')
903
935
  ```
904
936
 
905
937
  ### Security and Signing
906
938
 
907
- Webhook requests are signed with HMAC SHA256 using your webhook secret. The signature is sent in the `Signature` HTTP header.
939
+ Webhook requests are signed with HMAC SHA256 using your webhook secret. The signature is sent in the `Signature` HTTP header. Verify the signature against the raw request body before parsing JSON.
908
940
 
909
941
  #### Setting a Webhook Secret
910
942
 
@@ -915,11 +947,19 @@ await client.webhook.updateWebhookSecret('your-secret')
915
947
  #### Verifying Signatures
916
948
 
917
949
  ```typescript
918
- import { createHmac } from 'crypto'
950
+ import { createHmac, timingSafeEqual } from 'node:crypto'
951
+
952
+ function verifySpritzWebhook(rawBody: string, signature: string, secret: string) {
953
+ const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
954
+ const expectedBuffer = Buffer.from(expected, 'utf8')
955
+ const signatureBuffer = Buffer.from(signature, 'utf8')
919
956
 
920
- const expected = createHmac('sha256', WEBHOOK_SECRET).update(JSON.stringify(payload)).digest('hex')
957
+ if (expectedBuffer.length !== signatureBuffer.length) return false
958
+ return timingSafeEqual(expectedBuffer, signatureBuffer)
959
+ }
921
960
 
922
- if (expected !== request.headers['signature']) {
961
+ const signature = request.headers['signature']
962
+ if (!signature || !verifySpritzWebhook(rawBody, signature, WEBHOOK_SECRET)) {
923
963
  throw new Error('Invalid webhook signature')
924
964
  }
925
965
  ```