@siglume/direct-request-payment 0.4.1 → 0.4.3

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.
@@ -80,10 +80,79 @@ Returns:
80
80
  `nonce` must not contain `:` because the platform challenge string is delimited
81
81
  as `scheme:nonce:signature`.
82
82
 
83
+ ## `createDirectRequestPaymentChallengeSignature(secret, input)` / `create_direct_request_payment_challenge_signature(...)`
84
+
85
+ Returns just the HMAC-SHA256 hex digest (no `scheme:nonce:signature` wrapper),
86
+ for callers who assemble the challenge string themselves. This is the primitive
87
+ that `createDirectRequestPaymentChallenge` calls internally. The HMAC material is
88
+ `merchant:amount_minor:currency:nonce`.
89
+
90
+ ```ts
91
+ const signature = await createDirectRequestPaymentChallengeSignature(secret, {
92
+ merchant: "example_merchant",
93
+ amount_minor: 1200,
94
+ currency: "JPY",
95
+ nonce: "order_123-attempt_1",
96
+ });
97
+ ```
98
+
99
+ ```py
100
+ signature = create_direct_request_payment_challenge_signature(
101
+ secret=secret,
102
+ merchant="example_merchant",
103
+ amount_minor=1200,
104
+ currency="JPY",
105
+ nonce="order_123-attempt_1",
106
+ )
107
+ ```
108
+
109
+ In TypeScript the secret is the first positional argument and the rest are an
110
+ object. In Python every argument is keyword-only:
111
+ `create_direct_request_payment_challenge_signature(*, secret, merchant, amount_minor, currency, nonce)`.
112
+ Returns a `string` (TS) / `str` (Py).
113
+
83
114
  ## `verifyDirectRequestPaymentChallenge(secret, input)` / `verify_direct_request_payment_challenge(...)`
84
115
 
85
116
  Verifies a challenge against merchant, amount, currency, and secret. This is
86
- useful in tests and internal checkout assertions.
117
+ useful in tests and internal checkout assertions. Returns `boolean` (TS) /
118
+ `bool` (Py) — `true` only when the challenge scheme matches and the recomputed
119
+ signature is a timing-safe match.
120
+
121
+ ```ts
122
+ const ok = await verifyDirectRequestPaymentChallenge(secret, {
123
+ merchant: "example_merchant",
124
+ amount_minor: 1200,
125
+ currency: "JPY",
126
+ challenge: challengeString,
127
+ });
128
+ ```
129
+
130
+ In TypeScript the secret is positional and the rest is an object
131
+ (`verifyDirectRequestPaymentChallenge(secret, { merchant, amount_minor, currency, challenge })`).
132
+ In Python every argument is keyword-only:
133
+
134
+ ```py
135
+ ok = verify_direct_request_payment_challenge(
136
+ secret=secret,
137
+ merchant="example_merchant",
138
+ amount_minor=1200,
139
+ currency="JPY",
140
+ challenge=challenge_string,
141
+ )
142
+ ```
143
+
144
+ ## `parseDirectRequestPaymentChallenge(challenge)` / `parse_direct_request_payment_challenge(challenge)`
145
+
146
+ Splits a `scheme:nonce:signature` challenge string into its parts. Throws
147
+ `SiglumeDirectRequestPaymentError` (TS) / `DirectRequestPaymentError` (Py) when
148
+ the string is not exactly three non-empty colon-delimited parts. The `challenge`
149
+ argument is positional in both languages.
150
+
151
+ Returns:
152
+
153
+ - `scheme`
154
+ - `nonce`
155
+ - `signature`
87
156
 
88
157
  ## `createDirectRequestPaymentRecurringChallenge(input)` / `create_direct_request_payment_recurring_challenge(...)`
89
158
 
@@ -137,10 +206,59 @@ monthly auto-pay budget.
137
206
 
138
207
  Returns the same fields as the one-time challenge helper, plus `cadence`.
139
208
 
209
+ ## `createDirectRequestPaymentRecurringChallengeSignature(secret, input)` / `create_direct_request_payment_recurring_challenge_signature(...)`
210
+
211
+ Returns just the HMAC-SHA256 hex digest for a recurring approval, for callers who
212
+ assemble the challenge string themselves. This is the primitive that
213
+ `createDirectRequestPaymentRecurringChallenge` calls internally. The HMAC
214
+ material is `merchant:amount_minor:currency:cadence:nonce` and must stay
215
+ byte-identical to the server's recurring-challenge signer.
216
+
217
+ ```ts
218
+ const signature = await createDirectRequestPaymentRecurringChallengeSignature(secret, {
219
+ merchant: "example_merchant",
220
+ amount_minor: 980,
221
+ currency: "JPY",
222
+ cadence: "monthly",
223
+ nonce: "subscription_setup_4711",
224
+ });
225
+ ```
226
+
227
+ ```py
228
+ signature = create_direct_request_payment_recurring_challenge_signature(
229
+ secret=secret,
230
+ merchant="example_merchant",
231
+ amount_minor=980,
232
+ currency="JPY",
233
+ cadence="monthly",
234
+ nonce="subscription_setup_4711",
235
+ )
236
+ ```
237
+
238
+ In TypeScript the secret is positional and the rest is an object. In Python every
239
+ argument is keyword-only:
240
+ `create_direct_request_payment_recurring_challenge_signature(*, secret, merchant, amount_minor, currency, cadence, nonce)`.
241
+ Returns a `string` (TS) / `str` (Py).
242
+
140
243
  ## `verifyDirectRequestPaymentRecurringChallenge(secret, input)` / `verify_direct_request_payment_recurring_challenge(...)`
141
244
 
142
245
  Verifies a recurring approval challenge against merchant, amount, currency,
143
- cadence, and secret.
246
+ cadence, and secret. Returns `boolean` (TS) / `bool` (Py).
247
+
248
+ In TypeScript the secret is positional and the rest is an object
249
+ (`verifyDirectRequestPaymentRecurringChallenge(secret, { merchant, amount_minor, currency, cadence, challenge })`).
250
+ In Python every argument is keyword-only:
251
+
252
+ ```py
253
+ ok = verify_direct_request_payment_recurring_challenge(
254
+ secret=secret,
255
+ merchant="example_merchant",
256
+ amount_minor=980,
257
+ currency="JPY",
258
+ cadence="monthly",
259
+ challenge=challenge_string,
260
+ )
261
+ ```
144
262
 
145
263
  ## `directRequestPaymentChallengeHash(challenge)` / `direct_request_payment_challenge_hash(...)`
146
264
 
@@ -180,7 +298,9 @@ Input:
180
298
  - `merchant`: self-service merchant key, 3-64 chars using lowercase letters,
181
299
  numbers, `_`, or `-`
182
300
  - `display_name`: optional public merchant name
183
- - `billing_plan`: `launch`, `starter`, `growth`, or `pro`
301
+ - `billing_plan`: `launch`, `starter`, `growth`, or `pro` (default `launch`). The
302
+ legacy key `free` is also accepted as a compatibility input and maps to the
303
+ Launch tier; prefer `launch` in new code.
184
304
  - `billing_currency`: `JPY`; `USD` requires agreed USD/USDC billing terms
185
305
  - `webhook_callback_url`: HTTPS callback URL for signed payment events
186
306
  - `max_amount_minor`: optional billing mandate cap
@@ -191,6 +311,21 @@ Input:
191
311
  absolute origin such as `https://shop.example.com`; entries are normalized to
192
312
  bare, lowercased origins and deduped.
193
313
 
314
+ In addition to the `setupMerchant` inputs above, `setupCheckout` accepts these
315
+ orchestration toggles:
316
+
317
+ - `prepare_billing_mandate`: default `true`. When `false`, the billing mandate
318
+ step is skipped and `billing_mandate` in the result is `null`.
319
+ - `create_webhook_subscription`: optional. When omitted, a webhook subscription
320
+ is created only if `webhook_callback_url` is set. Set `false` to skip webhook
321
+ creation even when a callback URL is present (TS uses `?? Boolean(webhook_callback_url)`;
322
+ Py defaults to `bool(webhook_callback_url)`).
323
+ - `webhook_event_types`: optional `string[]` of event types for the created
324
+ subscription. When omitted the subscription defaults to
325
+ `direct_payment.confirmed` and `direct_payment.spent`.
326
+ - `webhook_description`: optional description for the created subscription;
327
+ defaults to `"<merchant> Direct Request Payment"`.
328
+
194
329
  Returns:
195
330
 
196
331
  - `merchant`: merchant account setup response
@@ -291,7 +426,7 @@ Calls:
291
426
  GET /v1/sdrp/direct-payments/checkout-sessions/{session_id}
292
427
  ```
293
428
 
294
- Returns a session status object with:
429
+ Returns a `HostedCheckoutSession` status object with:
295
430
 
296
431
  - `session_id`
297
432
  - `merchant`
@@ -301,13 +436,19 @@ Returns a session status object with:
301
436
  - `status`: one of `open`, `authenticated`, `paid`, `expired`, `cancelled`,
302
437
  `failed`
303
438
  - `challenge_hash`
304
- - `requirement_id`
439
+ - `requirement_id` (nullable until a requirement is created)
305
440
  - `success_url`
306
441
  - `cancel_url`
307
- - `expires_at`
308
- - `paid_at`
442
+ - `expires_at` (nullable)
443
+ - `authenticated_at` (nullable; set when the shopper signs into Siglume)
444
+ - `paid_at` (nullable; set when the payment confirms)
445
+ - `cancelled_at` (nullable; set when the shopper cancels)
446
+ - `created_at` (nullable)
309
447
  - `metadata_jsonb`
310
448
 
449
+ The TS `HostedCheckoutSession` interface also carries an index signature, so the
450
+ server may include additional pass-through fields.
451
+
311
452
  ### `getMerchant(merchant)` / `get_merchant(merchant)`
312
453
 
313
454
  Calls:
@@ -359,8 +500,10 @@ Use it with the authenticated buyer's Siglume bearer token. Developer Portal
359
500
 
360
501
  This client creates SDRP Standard Payment requirements for external merchant
361
502
  checkout flows. Micro Payment and Nano Payment are applied automatically by
362
- amount and settled on a weekly / monthly cadence; they are not created explicitly
363
- through this client.
503
+ amount and settled on account-assigned weekly / monthly slots; they are not
504
+ created explicitly through this client. Use the statement APIs below to see
505
+ open-period usage, the close time, the final-notice schedule, and settled /
506
+ unsettled / past-due revenue.
364
507
 
365
508
  Payment requirements include `fee_bps` from the Siglume platform. The SDK does
366
509
  not calculate merchant plan fees locally; see [Pricing](./pricing.md).
@@ -401,6 +544,61 @@ Input:
401
544
  - `allowance_amount_minor`: optional positive integer
402
545
  - `metadata`: optional JSON object
403
546
 
547
+ ### `getPaymentRequirement(requirement_id)` / `get_payment_requirement(requirement_id)`
548
+
549
+ Calls:
550
+
551
+ ```text
552
+ GET /v1/sdrp/direct-payments/requirements/{requirement_id}
553
+ ```
554
+
555
+ Fetches the current state of a payment requirement (status, `transaction_request`,
556
+ `approve_transaction_request`, `chain_receipt_id`, etc.) by id. The
557
+ `requirement_id` argument is positional in both languages. Returns the same
558
+ requirement object shape as `createPaymentRequirement`.
559
+
560
+ ```ts
561
+ const requirement = await siglume.getPaymentRequirement(requirementId);
562
+ ```
563
+
564
+ ```py
565
+ requirement = siglume.get_payment_requirement(requirement_id)
566
+ ```
567
+
568
+ ### `executePreparedTransaction(payload)` / `execute_prepared_transaction(payload)`
569
+
570
+ Calls:
571
+
572
+ ```text
573
+ POST /v1/market/web3/transactions/execute-prepared
574
+ ```
575
+
576
+ The raw prepared-transaction executor. It posts a prepared-transaction payload
577
+ (`transaction_request`, `receipt_kind`, `reference_type`, `reference_id`,
578
+ `metadata`, `await_finality`) to the marketplace web3 route and returns the
579
+ execution result (`{ receipt?, finalization?, ... }`). The `payload` argument is
580
+ positional in both languages.
581
+
582
+ `executePaymentTransaction` / `execute_payment_transaction` and
583
+ `executeAllowanceTransaction` / `execute_allowance_transaction` are convenience
584
+ wrappers over this method: they build the payload from the requirement (via
585
+ [`buildPaymentExecutionPayload`](#payload-builders) /
586
+ [`buildAllowanceExecutionPayload`](#payload-builders)) and call
587
+ `executePreparedTransaction` for you. Call `executePreparedTransaction` directly
588
+ only when you build the payload yourself.
589
+
590
+ ```ts
591
+ const result = await siglume.executePreparedTransaction(
592
+ buildPaymentExecutionPayload(requirement, { await_finality: true }),
593
+ );
594
+ ```
595
+
596
+ ```py
597
+ result = siglume.execute_prepared_transaction(
598
+ build_payment_execution_payload(requirement, await_finality=True),
599
+ )
600
+ ```
601
+
404
602
  ### `executeAllowanceTransaction(requirement)` / `execute_allowance_transaction(...)`
405
603
 
406
604
  Executes `requirement.approve_transaction_request` through:
@@ -433,19 +631,301 @@ Input may include:
433
631
  - `await_timeout_seconds`
434
632
  - `await_poll_seconds`
435
633
 
634
+ ### `request<T>(method, path, json_body?)` (TypeScript only)
635
+
636
+ Calls an authenticated Siglume JSON endpoint using the same bearer token and
637
+ base URL configured on `DirectRequestPaymentClient`.
638
+
639
+ `path` is the API path after `/v1`, for example
640
+ `/sdrp/metered/my-summary`. The helper expects a JSON response. Use raw
641
+ `fetch`, `curl`, or your HTTP client for CSV exports.
642
+
643
+ Python does not expose a public generic request helper in this release. Use
644
+ ordinary HTTPS requests with the same Siglume bearer token for endpoints that do
645
+ not yet have a named Python SDK method.
646
+
647
+ ## Metered Statement APIs
648
+
649
+ Micro Payment and Nano Payment require operational reconciliation after usage is
650
+ accepted. The immediate requirement response does not tell a merchant how much
651
+ is settled, when the batch can first debit, or why a buyer is past due.
652
+
653
+ See the full operating guide:
654
+ [Micro / Nano Statements and Notices](./metered-statements.md).
655
+
656
+ ### Buyer summary
657
+
658
+ ```text
659
+ GET /v1/sdrp/metered/my-summary
660
+ ```
661
+
662
+ Common query parameters:
663
+
664
+ - `plan_type`: `micro` or `nano`
665
+ - `token_symbol`: `JPYC` or `USDC`
666
+
667
+ TypeScript:
668
+
669
+ ```ts
670
+ const summary = await siglume.request<{
671
+ role: "buyer";
672
+ open_periods: Array<Record<string, unknown>>;
673
+ settlement_batches: Array<Record<string, unknown>>;
674
+ past_due_blocks: Array<Record<string, unknown>>;
675
+ balance_sufficiency: Record<string, unknown>;
676
+ }>("GET", "/sdrp/metered/my-summary?plan_type=micro&token_symbol=JPYC");
677
+ ```
678
+
679
+ Use `open_periods` for current-period estimated debit,
680
+ `settlement_batches` for closed / scheduled / settled periods, and
681
+ `past_due_blocks` to explain `METERED_SETTLEMENT_PAST_DUE`.
682
+
683
+ ### Buyer usage and settlement lists
684
+
685
+ ```text
686
+ GET /v1/sdrp/metered/my-usage-events
687
+ GET /v1/sdrp/metered/my-settlement-batches
688
+ ```
689
+
690
+ Buyer-facing amount names are centered on the debit:
691
+
692
+ - `estimated_buyer_debit_minor`
693
+ - `provider_usage_amount_minor`
694
+ - `gross_buyer_debit_minor`
695
+ - `buyer_debit_minor`
696
+ - `protocol_fee_minor`
697
+
698
+ ### Provider summary
699
+
700
+ ```text
701
+ GET /v1/sdrp/metered/provider/summary
702
+ ```
703
+
704
+ Common query parameters:
705
+
706
+ - `plan_type`: `micro` or `nano`
707
+ - `token_symbol`: `JPYC` or `USDC`
708
+ - `listing_id`
709
+ - `capability_key`
710
+
711
+ TypeScript:
712
+
713
+ ```ts
714
+ const providerSummary = await siglume.request<{
715
+ role: "provider";
716
+ timezone: string;
717
+ open_periods: Array<Record<string, unknown>>;
718
+ periods: Array<Record<string, unknown>>;
719
+ totals: Record<string, string>;
720
+ }>(
721
+ "GET",
722
+ "/sdrp/metered/provider/summary?plan_type=micro&token_symbol=JPYC",
723
+ );
724
+ ```
725
+
726
+ Use `open_periods` for current-period expected revenue, `periods` for closed
727
+ and historical batches, and `totals` to separate settled, unsettled, and
728
+ past-due provider amounts. Do not recognize Micro / Nano revenue until the batch
729
+ is `settled` and has an on-chain receipt.
730
+
731
+ ### Provider usage and settlement detail
732
+
733
+ ```text
734
+ GET /v1/sdrp/metered/provider/usage-events
735
+ GET /v1/sdrp/metered/provider/settlement-batches
736
+ GET /v1/sdrp/metered/provider/settlement-batches/{settlement_batch_id}
737
+ ```
738
+
739
+ Provider-facing amount names:
740
+
741
+ - `provider_receivable_minor`
742
+ - `gross_buyer_debit_minor`
743
+ - `buyer_debit_minor`
744
+ - `protocol_fee_minor`
745
+ - `settled_provider_receivable_minor`
746
+ - `unsettled_provider_receivable_minor`
747
+ - `past_due_provider_receivable_minor`
748
+
749
+ Schedule and execution fields:
750
+
751
+ - `period_start`, `period_end`, `close_at`
752
+ - `expected_scheduled_debit_at`
753
+ - `scheduled_debit_at`
754
+ - `not_before_attempt_at`
755
+ - `execution_status`
756
+ - `latest_execution_attempt_status`
757
+ - `chain_receipt_id`
758
+ - `usage_event_digest`
759
+ - `attempt_count`
760
+ - `next_attempt_at`
761
+
762
+ Failure fields are sanitized for public display:
763
+
764
+ - `failure_reason_code`
765
+ - `failure_reason_label`
766
+ - `failure_reason_help`
767
+ - `support_reference`
768
+
769
+ Provider APIs do not expose raw `buyer_user_id`, buyer email, buyer wallet
770
+ address, relayer id, nonce, gas data, raw RPC errors, or raw
771
+ `failure_message`. Use `buyer_period_ref` for provider-side reconciliation
772
+ within a period.
773
+
774
+ ### Provider CSV export
775
+
776
+ ```text
777
+ GET /v1/sdrp/metered/provider/settlement-batches/{settlement_batch_id}/usage-events.csv
778
+ ```
779
+
780
+ The CSV is not JSON. Fetch it with raw `fetch`, `curl`, or your HTTP client:
781
+
782
+ ```bash
783
+ curl https://siglume.com/v1/sdrp/metered/provider/settlement-batches/<batch-id>/usage-events.csv \
784
+ -H "Authorization: Bearer <provider-siglume-bearer-token>" \
785
+ -o sdrp-metered.csv
786
+ ```
787
+
788
+ Columns:
789
+
790
+ ```text
791
+ metered_usage_id,created_at,plan_type,settlement_cadence,period_start,period_end,listing_id,capability_key,operation_key,currency,token_symbol,provider_receivable_minor,protocol_fee_minor,gross_buyer_debit_minor,rounding_delta_minor,buyer_debit_minor,status,settlement_batch_id,buyer_period_ref
792
+ ```
793
+
794
+ The CSV uses `buyer_period_ref`, not raw buyer account identifiers.
795
+
796
+ ## Payload Builders
797
+
798
+ These pure functions build the `execute-prepared` payload from a payment
799
+ requirement, for callers who execute the transaction themselves (rather than via
800
+ the `executePaymentTransaction` / `executeAllowanceTransaction` wrappers). They
801
+ make no network calls.
802
+
803
+ ### `buildPaymentExecutionPayload(requirement, options)` / `build_payment_execution_payload(...)`
804
+
805
+ Builds the payment-transaction payload from `requirement.transaction_request`
806
+ with `receipt_kind = "sdrp_direct_payment"`.
807
+
808
+ In TypeScript `options` is an optional object `{ await_finality?, metadata? }`.
809
+ In Python the options are keyword-only:
810
+ `build_payment_execution_payload(requirement, *, await_finality=False, metadata=None)`.
811
+ Returns the prepared-transaction payload object.
812
+
813
+ ### `buildAllowanceExecutionPayload(requirement, options)` / `build_allowance_execution_payload(...)`
814
+
815
+ Builds the allowance/approval-transaction payload from
816
+ `requirement.approve_transaction_request` with
817
+ `receipt_kind = "sdrp_direct_payment_allowance"`. Throws
818
+ `SiglumeDirectRequestPaymentError` (TS) / `DirectRequestPaymentError` (Py) when
819
+ the requirement carries no allowance approval transaction. Same options shape as
820
+ `buildPaymentExecutionPayload`.
821
+
822
+ ### `buildPreparedTransactionExecutionPayload(requirement, transaction_request, options)` / `build_prepared_transaction_execution_payload(...)`
823
+
824
+ The lower-level builder both of the above call. It merges
825
+ `transaction_request.metadata_jsonb` with any `options.metadata`, and sets
826
+ `reference_type = "sdrp_direct_payment_requirement"` and `reference_id =
827
+ requirement.requirement_id`.
828
+
829
+ In TypeScript `options` is required and must include `receipt_kind`:
830
+ `{ receipt_kind, await_finality?, metadata? }`. In Python the third argument is
831
+ the `transaction_request` and the rest are keyword-only:
832
+ `build_prepared_transaction_execution_payload(requirement, transaction_request, *, receipt_kind, await_finality=False, metadata=None)`.
833
+ Returns the prepared-transaction payload object.
834
+
436
835
  ## Webhook Helpers
437
836
 
438
- - `buildWebhookSignatureHeader(secret, body)` for tests
439
- - `verifyWebhookSignature(secret, body, header)`
440
- - `verifyDirectRequestPaymentWebhook(secret, body, header)`
441
- - `parseDirectRequestPaymentWebhookEvent(payload)`
442
- - Python equivalents use snake_case:
443
- `build_webhook_signature_header`, `verify_webhook_signature`,
444
- `verify_direct_request_payment_webhook`, and
445
- `parse_direct_request_payment_webhook_event`.
446
-
447
- `verifyDirectRequestPaymentWebhook` verifies the signature and parses the event
448
- in one call.
837
+ ### `computeWebhookSignature(secret, body, options)` / `compute_webhook_signature(secret, body, *, timestamp)`
838
+
839
+ Returns the bare HMAC-SHA256 hex digest over `"<timestamp>.<body>"`. This is the
840
+ primitive `buildWebhookSignatureHeader` / `verifyWebhookSignature` use. In
841
+ TypeScript `options` is `{ timestamp: number }`; in Python `timestamp` is a
842
+ keyword-only `int`. `body` may be raw bytes, a string, or a JSON object.
843
+
844
+ ### `buildWebhookSignatureHeader(secret, body, options)` / `build_webhook_signature_header(secret, body, *, timestamp=None)`
845
+
846
+ Returns a `t=<timestamp>,v1=<signature>` header string. Mainly for tests /
847
+ mocking inbound webhooks. In TypeScript `options` is an optional
848
+ `{ timestamp?: number }` (defaults to now); in Python `timestamp` is a
849
+ keyword-only optional `int`.
850
+
851
+ ### `verifyWebhookSignature(secret, body, signature_header, options)` / `verify_webhook_signature(secret, body, signature_header, *, tolerance_seconds=300, now=None)`
852
+
853
+ Verifies the `Siglume-Signature` header against the raw `body`. Throws
854
+ `SiglumeWebhookSignatureError` (TS) / `SiglumeWebhookSignatureError` (Py) when
855
+ the timestamp is outside tolerance or the signature does not match. In TypeScript
856
+ `options` is `{ tolerance_seconds?, now? }`; in Python those are keyword-only
857
+ (`tolerance_seconds` defaults to `DEFAULT_WEBHOOK_TOLERANCE_SECONDS` = 300).
858
+ Returns `{ timestamp, signature }`.
859
+
860
+ ### `parseDirectRequestPaymentWebhookEvent(payload)` / `parse_direct_request_payment_webhook_event(payload)`
861
+
862
+ Validates and normalizes a parsed webhook event object (requires `id`, `type`,
863
+ `api_version`, `occurred_at`, and an object `data`). Throws
864
+ `SiglumeWebhookPayloadError` on a malformed event, or when a
865
+ `direct_payment.confirmed` event does not carry `data.mode = "external_402"`. The
866
+ `payload` argument is positional in both languages.
867
+
868
+ ### `verifyDirectRequestPaymentWebhook(secret, body, signature_header, options)` / `verify_direct_request_payment_webhook(secret, body, signature_header, *, tolerance_seconds=300, now=None)`
869
+
870
+ Verifies the signature and parses the event in one call. Returns
871
+ `{ event, verification }` (TS) / `{"event": ..., "verification": ...}` (Py). Same
872
+ options shape as `verifyWebhookSignature` (keyword-only in Python).
873
+
874
+ Webhook-verification trio (typical inbound webhook handler):
875
+
876
+ ```ts
877
+ import { verifyDirectRequestPaymentWebhook } from "@siglume/direct-request-payment";
878
+
879
+ const { event, verification } = await verifyDirectRequestPaymentWebhook(
880
+ process.env.SIGLUME_WEBHOOK_SECRET!,
881
+ rawRequestBody, // the RAW body bytes/string, not re-stringified JSON
882
+ request.headers["siglume-signature"],
883
+ );
884
+ // event.type === "direct_payment.confirmed" -> fulfill once; verification.timestamp is the signed time
885
+ ```
886
+
887
+ ```py
888
+ from siglume_direct_request_payment import verify_direct_request_payment_webhook
889
+
890
+ verified = verify_direct_request_payment_webhook(
891
+ os.environ["SIGLUME_WEBHOOK_SECRET"],
892
+ raw_request_body, # the RAW body bytes/string
893
+ siglume_signature_header,
894
+ )
895
+ event = verified["event"]
896
+ # event["type"] == "direct_payment.confirmed" -> fulfill once
897
+ ```
898
+
899
+ ## Exported Constants
900
+
901
+ Both packages export these importable constants:
902
+
903
+ | Constant | Value |
904
+ | --- | --- |
905
+ | `DEFAULT_SIGLUME_API_BASE` | `https://siglume.com/v1` |
906
+ | `DIRECT_REQUEST_PAYMENT_CHALLENGE_SCHEME` | `siglume-external-402-v1` |
907
+ | `DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME` | `siglume-external-402-recurring-v1` |
908
+ | `DIRECT_REQUEST_PAYMENT_MODE` | `external_402` |
909
+ | `DIRECT_REQUEST_PAYMENT_RECEIPT_KIND` | `sdrp_direct_payment` |
910
+ | `DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND` | `sdrp_direct_payment_allowance` |
911
+ | `DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE` | `sdrp_direct_payment_requirement` |
912
+ | `DEFAULT_WEBHOOK_TOLERANCE_SECONDS` | `300` |
913
+
914
+ The `external_402` / `siglume-external-402-*` values are legacy wire-compat
915
+ identifiers, not public product names (see the README "Compatibility Notes").
916
+
917
+ ## Aliases
918
+
919
+ For legacy wire-compat naming, the following exported names are aliases of the
920
+ preferred `DirectRequestPayment*` functions. They are identical functions; new
921
+ code should prefer the `DirectRequestPayment*` names.
922
+
923
+ | Alias (TS) | Alias (Py) | Preferred function |
924
+ | --- | --- | --- |
925
+ | `createExternal402Challenge` | `create_external_402_challenge` | `createDirectRequestPaymentChallenge` / `create_direct_request_payment_challenge` |
926
+ | `verifyExternal402Challenge` | `verify_external_402_challenge` | `verifyDirectRequestPaymentChallenge` / `verify_direct_request_payment_challenge` |
927
+ | `createExternal402RecurringChallenge` | `create_external_402_recurring_challenge` | `createDirectRequestPaymentRecurringChallenge` / `create_direct_request_payment_recurring_challenge` |
928
+ | `verifyExternal402RecurringChallenge` | `verify_external_402_recurring_challenge` | `verifyDirectRequestPaymentRecurringChallenge` / `verify_direct_request_payment_recurring_challenge` |
449
929
 
450
930
  ## Errors
451
931