hashry-pay
The official Hashry Pay server SDK for Python. Use it on your server to create payments, read their status and verify webhooks.
Full docs: https://docs.hashrytech.com/pay
This is an early release (0.0.1). The API can change before 1.0.
Install
pip install hashry-pay
Python 3.11 or newer. The only dependency is httpx.
Quick start
1. Create the client
Get the client ID and secret from your channel's API access page in the Hashry admin. Keep them in environment variables on your server.
import os
from hashry_pay import Hashry
hashry = Hashry(
client_id=os.environ["HASHRY_CLIENT_ID"],
client_secret=os.environ["HASHRY_CLIENT_SECRET"],
environment="sandbox", # "live" with a live key
)
A test key (hsk_test_) works only with environment="sandbox", and a live key
(hsk_live_) only with "live". The client refuses the wrong pair. Keys made
before the prefixes still work, with a one-time warning.
2. Create a payment on your server
intent = hashry.payment_intents.create(
amount="2500.00", # a string or Decimal, never a float
currency="JMD",
description=f"Order #{order.number}",
external_reference=str(order.id),
payer={"email": order.email}, # an email or a phone is required
return_url="https://shop.example.com/pay/done",
cancel_url="https://shop.example.com/cart",
presentation="embedded",
embed_origin="https://shop.example.com",
idempotency_key=f"order-{order.id}-pay-1",
)
You choose the idempotency_key, one per payment. The SDK never makes one up.
Sending the same key and details again is safe: you get the same payment back.
A declined card does not end an embedded payment. The SDK always asks for retry after a decline
(retry_after_decline=True) on an embedded create. It is not a parameter, so you cannot turn it off.
After a declined card the frame shows the card form again and the payment stays redirect_issued.
No payment.failed webhook is sent for that card. When the last card the frame allows (3) is
declined, the payment becomes requires_payment_method; then start a new attempt with
create_attempt. The docs have a complete route that starts, renews and retries a payment:
https://docs.hashrytech.com/pay/python. The SDK raises HashryError if the API answers an embedded create without
retry_after_decline on.
3. Hand four values to the browser
return {
"embed_url": intent.embed_url,
"transaction_uid": intent.transaction_uid,
"status_url": intent.status_url,
"status_token": intent.status_token,
}
embed_urlandtransaction_uidload the card form (with@hashrytech/pay-reactor@hashrytech/pay-svelte).embed_urlis a bearer link: don't log it or store it.status_urlandstatus_tokenlet the page show the status while the shopper waits (pollStatus). The token reads only this payment's status. It can't confirm a payment.
Send nothing else. The client secret never goes to the browser.
4. Confirm on your server
When the card form says it is done, or the shopper lands on your return_url,
ask Hashry. refresh checks with the gateway first.
intent = hashry.payment_intents.refresh(intent_uid)
if intent.status == "succeeded":
mark_order_paid(intent.external_reference, intent.uid)
5. Verify webhooks
from django.db import transaction
from hashry_pay import WebhookError, webhooks
def hashry_webhook(request): # Django shown; any framework works
try:
event = webhooks.verify(
request.body, # the raw bytes, not parsed JSON
path=request.path,
headers=request.headers,
secret=os.environ["HASHRY_WEBHOOK_SECRET"],
)
except WebhookError:
return HttpResponse(status=400)
# Read from Hashry before opening your database transaction.
intent = hashry.payment_intents.refresh(event.payment_intent_uid) if event.payment_intent_uid else None
with transaction.atomic():
if not seen_events.add_if_new(event.id):
return HttpResponse(status=200) # already handled
if intent is not None:
apply_payment_update(intent) # your amount, currency, and order checks
return HttpResponse(status=200)
verify checks the signature in constant time and refuses a timestamp more
than 5 minutes old or ahead. During a webhook secret rotation, pass
secrets=[new_secret, old_secret]. There is no way to read an event without
verifying it.
Other resources
| Resource | Methods |
|---|---|
payment_intents |
create, retrieve, list, refresh, cancel, create_attempt |
payment_links |
create, retrieve, list, cancel |
payment_methods |
list, delete, forget_customer |
subscription_intents |
create, retrieve, list |
subscriptions |
retrieve, list, charges, cancel, uncancel, update_card_session, retry_now |
channel |
self |
AsyncHashry has the same methods. Use await, and async with or aclose().
Results are typed objects. Their raw attribute holds the full response, so new
API fields are there before the SDK models them. Money values are strings with 2
decimal places.
Errors and retries
- Every error is a
hashry_pay.HashryError. - API refusals raise
ApiErrorwithcode,statusanddetail, or a subclass:InvalidRequestError,AuthenticationError,PermissionDeniedError,NotFoundError,ConflictError,IdempotencyError,RateLimitError,ServerError. - No answer raises
ApiConnectionError(orApiTimeoutError). error.outcome_unknownis true when Hashry may have acted anyway. Replay with the same idempotency key, or read the payment. Never create a new one.
The SDK retries network errors, 5xx and 429 answers twice (max_retries), with
backoff. It retries only reads and calls with an idempotency key. Other 4xx
answers are never retried. The access token is cached and renewed before it
expires, and once more after a 401.
Security rules
- Keep the client secret (
hsk_live_,hsk_test_) and the webhook secret (hwhsec_) on your server. Use@hashrytech/pay-build-guardto fail a frontend build that bundles one. - Give the browser only the intent
uid,embed_url,transaction_uidandstatus_token(hst_). - Treat the card form's "completed" message and webhooks as cues. Confirm with
refreshorretrievebefore you fulfil an order. - Store each webhook event ID and apply each event once.
- The SDK masks secrets, the access token,
embed_urland status tokens inrepr, logs (loggerhashry_pay) and errors.
Supported versions
Python 3.11 or newer. CI tests 3.11, 3.12, 3.13 and 3.14.
Support and security
Questions: support@hashrytech.com. For a security problem, email the same address with "Security" in the subject.
License
Apache License 2.0. See LICENSE and NOTICE.
Webhook deduplication uses the event ID inside the signed body. The verifier
rejects a different X-Hashry-Event-Id header. For older deliveries without
a signed event ID, it returns a stable hash of the signed body. Treat the
returned ID as opaque. Commit the ID and your payment changes in one database
transaction. A failed transaction must leave the event available for retry.
Metadata
Release files for hashry-pay 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| hashry_pay-0.1.0.tar.gz | 74.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hashry_pay-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 117.5 kB
Release files / hashry_pay-0.1.0.tar.gz
| Download URL | hashry_pay-0.1.0.tar.gz |
|---|---|
| Size | 74.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7883ed24a795616d317be9293257cd1726150fb9f0ac5ef87677ee2b7b1b6312
|
|
BLAKE2b-256 checksum How to use checksums |
3490a8a8fed54fa23cf10511dc00165641b4a6e40278a43e18ec157ef854d885
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / hashry_pay-0.1.0-py3-none-any.whl
| Download URL | hashry_pay-0.1.0-py3-none-any.whl |
|---|---|
| Size | 42.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d032a7d96578c834fca6e5e0e36479b794a6aa693ba917fda1e8e194c42c42e4
|
|
BLAKE2b-256 checksum How to use checksums |
0241da049942c9ce94f62d05ed41de2b49a7f971b88137d6d34355ba763a450c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for hashry-pay 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for hashry_pay-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.