Skip to main content

timbro-payments

The Python SDK for Timbro Payments: create payments and refunds, read merchant accounts, prepare business certification, manage webhook endpoints, and verify signed webhooks. It needs Python 3.11 or newer and depends only on httpx and timbro-protocol.

pip install timbro-payments

Take a payment

from timbro_payments import TimbroPayments, payment_reference_for_source

with TimbroPayments(api_key=secret_key, application="my-erp/1.0") as timbro:
    payment = timbro.payments.create({
        "paymentReference": payment_reference_for_source({"id": "order-123", "version": "1"}),
        "amount": {"currency": "DOP", "value": "1250.00"},
        "description": "Servicio",
        "tax": {"includedAmount": "190.68"},
    })
    redirect_to(payment["checkout"]["url"])

    payment = timbro.payments.retrieve(payment["id"])
    for refund in timbro.refunds.list_all(payment_id=payment["id"]):
        ...

Requests and responses are plain dictionaries with the API's camelCase keys, typed as TypedDicts in timbro_payments.types. Money is a decimal string. Fields and enum values added by a later API version pass through unchanged. A declined attempt is not an error and does not end the Payment: it stays requires_customer_action with lastDecline, and each declined attempt sends payment.declined.

AsyncTimbroPayments has the same namespaces: await each call and use async for with list_all.

timbro.mode is "test" for an sk_test_ key, "live" for an sk_live_ key, and None for any other key form, so an integration can show which mode it is configured for without a call.

Namespace Methods
payments create, retrieve, list, list_all, cancel, create_invoice, attach_invoice, update_invoice_choice, update_invoice_recipient
refunds create, retrieve, list, list_all, attach_invoice
merchant_accounts retrieve, list, list_all
webhook_endpoints create, retrieve, list, list_all, delete, suspend, reactivate, rotate_secret, list_deliveries, list_all_deliveries, replay
business retrieve, update, configure_invoicing, import_signing_certificate, issue_test_invoice, issue_invoice_key, checkout_policy.retrieve, checkout_policy.update, certification.retrieve, certification.start, certification.submit_evidence
merchant_onboarding verify_rnc
api_keys rotate

Path identifiers are positional, the request body is the positional request, and filters and options are keyword-only.

Prepare a business for DGII certification

Certification is scoped to the business selected by the API credential. Read its available paths, start the recommended one, then resume the same process by reading it again. Persist each command's idempotency key before sending it and reuse that key only when retrying the same command.

current = timbro.business.certification.retrieve()
if current["status"] == "not_started":
    current = timbro.business.certification.start(
        {"path": "external_software"},
        idempotency_key="merchant-onboarding-123-certification-start",
    )

if current["status"] == "in_progress":
    if current["requirements"]:
        for requirement in current["requirements"]:
            print(f"Complete the {requirement['owner']} requirement: {requirement['code']}")
    else:
        step = next(
            (
                step
                for step in current["steps"]
                if step["number"] == 1 and step["status"] == "action_required"
            ),
            None,
        )
        if step is not None:
            print(f"Next step {step['number']}: {step['title']} ({step['actor']})")
            # The authorized person completes this action in DGII before reporting it to Timbro.

After the authorized person completes a DGII step, report its real result with timbro.business.certification.submit_evidence(), using the current revision, the step number, and the same persisted idempotency key if retrying. The result records the person's report; even an approved outcome remains user-reported and does not qualify the business for DGII production. Legal documents and DGII portal actions stay with the authorized person. business.import_signing_certificate() returns a MerchantBusinessCertification when its request uses purpose="certification"; requests with mode="test" or mode="live" return MerchantBusiness. Timbro's protected Portal handoff is the recommended place for a person to upload the certification certificate and password. Never send either through an agent conversation.

Retries and idempotency

The client retries at most twice (max_retries=2) after 408, 429, 500, 502, 503, 504, and connection failures. It waits 0.1 seconds, then 0.2, and honors a Retry-After of up to two seconds; a longer Retry-After ends the call with retry_after_seconds set. Reads and other operations the contract marks safe always retry. Other writes retry only with an idempotency key.

payments.create, payments.cancel, payments.create_invoice, payments.attach_invoice, refunds.create, and refunds.attach_invoice derive their idempotency key from the payment reference, the payment, the refund reference, or the attached Invoice and its payment or refund, so repeating a call is safe. Other writes that need a key take idempotency_key= and require it. Derive that key from your durable record of the command, such as the order line it serves, and send the same key on every retry and replay. Never generate a random key per attempt: an ERP that replays a transaction would then run the command twice. An explicit key always wins.

Timeouts

timeout (30 seconds by default) limits each phase of one attempt separately: waiting for a pooled connection, connecting, writing, and each wait for response bytes. When Timbro stops answering, an attempt ends after timeout, and a call makes at most max_retries + 1 attempts with up to two seconds between them. A stalled call therefore lasts about (max_retries + 1) * timeout plus four seconds: about 94 seconds with the defaults. Slow connects and writes can add their own phase limits on top. There is no overall deadline. A worker bound to a request, such as an Odoo worker with a 120-second limit, should pass smaller values, for example TimbroPayments(api_key=key, timeout=10, max_retries=1), which bounds a call near 22 seconds. A call can also pass timeout= to override the default for that call.

httpx logs each request URL, including the references in a list filter, at INFO on the httpx logger. Raise that logger's level to keep them out of your logs:

import logging

logging.getLogger("httpx").setLevel(logging.WARNING)

With your own http_client, that client's timeout applies unless a call passes timeout=. httpx finds CA certificates through SSL_CERT_FILE and SSL_CERT_DIR; it does not read REQUESTS_CA_BUNDLE. A host behind TLS inspection passes an http_client built with its CA bundle, such as httpx.Client(verify=ssl_context).

Errors

Every error is a TimbroError with code, message, request_id, operation, method, retryable, outcome_unknown, recovery, and recovery_method. operation and recovery are contract operation IDs, such as createPayment; method and recovery_method are the SDK methods that send them, such as payments.create and payments.list. The text never contains the API key, a request body, a header, or a Checkout URL. Errors can be pickled, so a job queue can carry them.

timbro-payments and timbro-invoices raise the same classes from timbro-protocol, so one except TimbroError handles both. To treat one package's calls differently, branch on method or operation:

from timbro_payments import TimbroError

try:
    timbro.payments.cancel(payment["id"])
except TimbroError as error:
    if error.method == "payments.cancel" and error.code == "payment_cancel_not_allowed":
        payment = timbro.payments.retrieve(payment["id"])
    else:
        raise
Error When
InvalidRequestError 400 or 422; problems lists each field at fault
AuthenticationError 401
PermissionDeniedError 403, such as a key without the needed grant
NotFoundError 404
ConflictError 409
RateLimitError 429; see retry_after_seconds
ServerError 5xx
APIError Any other failure status, and the base of the classes above
APIConnectionError, APITimeoutError No response arrived; code says what that means, below
UnexpectedResponseError A response outside the published contract, including a redirect, or one that names another resource than the one a read or an action addressed (response_identity_mismatch)

When no response arrives, code separates three cases:

code Meaning
connection_failed The request never left, or a read broke off. Nothing changed at Timbro.
request_timed_out A read timed out. Nothing changed at Timbro.
outcome_unknown A write may have reached Timbro. outcome_unknown is true.

@timbro/payments reports all three as transport_failure with status 0, and does not say whether a write may have run.

When outcome_unknown is true, a write may have taken effect even though the call failed: an earlier attempt or the last one reached Timbro and got no final answer. The error text says what to do next. Repeat the call with the same idempotency key, or find the result with the recovery method. payments.create recovers with payments.list(payment_reference=...) and refunds.create with refunds.list(payment_id=..., refund_reference=...), so a lost create is found without its ID.

Routes outside the published contract

Prefer the typed methods. For a route the published contract does not include yet, such as a Stamp installation's own readiness read, request sends one call through the same client, with the same API key, user agent, redirect refusal, 10 MiB limit, and errors:

readiness = timbro.request("GET", "/v1/stamp/readiness")

path must be a /v1/ path; an absolute URL, a query or fragment in the path, and an empty or dot segment raise ValueError, and query= carries the filters. The answer is the decoded JSON object, or None for an empty one, and nothing checks its shape. A GET is retried when safe; any other method is retried only when you pass idempotency_key=. An error it raises has method "request", operation set to the method and path, such as "GET /v1/stamp/readiness", and no recovery. AsyncTimbroPayments clients have the same method to await.

Webhooks

Verification needs no API key:

from timbro_payments import webhooks

event = webhooks.verify(body=raw_body, headers=request_headers, secret=webhook_secret)
inbox.store(event)  # Deduplicate by event["id"], then acknowledge.

if webhooks.is_known_event(event) and event["type"] == "payment.succeeded":
    payment = timbro.payments.retrieve(event["data"]["id"])

Pass the raw body bytes and every secret that is valid during a rotation. Events of a type this version does not know verify and return as UnknownWebhookEvent. A failure raises WebhookVerificationError, whose reason names the check.

Testing

timbro_payments.testing builds schema-valid resources and signs webhook fixtures. testing.payment(status) and testing.refund(status) fill the members each status carries: a succeeded Payment has capturedPayment allocations that sum to totalAmount and no nextAction, testing.payment(declined=True) carries the lastDecline of a declined attempt, and a declined Refund has its decline. A create must answer with the identity it sent, so a fake create passes it back as an override. testing.api_error(status, code, method="payments.cancel") builds the APIError subclass that method raises for that status. Fake the API by giving the client an httpx.MockTransport:

import json

import httpx
from timbro_payments import TimbroPayments, testing

def handler(request: httpx.Request) -> httpx.Response:
    sent = json.loads(request.content)
    payment = testing.payment("succeeded", paymentReference=sent["paymentReference"])
    return httpx.Response(201, json=payment)

timbro = TimbroPayments(
    api_key="sk_test_123",
    http_client=httpx.Client(transport=httpx.MockTransport(handler)),
)

body = b'{"id":"event_...","type":"payment.succeeded"}'
headers = testing.sign_webhook(body, "whsec_...", webhook_id="event_...")

Migrating from 0.10

0.11 is a breaking minor for every Timbro SDK, so upgrade timbro-payments, timbro-invoices, and timbro-protocol together.

  • A decline no longer ends a Payment. declined is gone from the status values: a declined attempt returns the Payment to requires_customer_action and sets lastDecline (code, message, attemptedAt), which replaces decline and drops canTryAgain. Only succeeded, canceled, and expired end a Payment. Subscribe to payment.declined to hear about each declined attempt.
  • invoicing.deliveryEmail on creation is now invoicing.recipientEmail, and the Payment reports it back.
  • Creation accepts expiresAfterSeconds (300 to 86400) to set the checkout window.
  • A succeeded Payment carries refundable; read refundable.automaticAmount before creating a Refund to know whether it runs without an operator.

License

No license has been granted for this package yet. The license will be declared before the first public release.

Metadata

Release files for timbro-payments 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for timbro-payments 0.12.0
File Interpreter ABI Platform
timbro_payments-0.12.0-py3-none-any.whl Python 3 none any Details

Release files / timbro_payments-0.12.0-py3-none-any.whl

Download URL timbro_payments-0.12.0-py3-none-any.whl
Size 55.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f559a7da924421e55c22a2bafee384ed62e4794e00dd82386b733c25958888ab
BLAKE2b-256 checksum
How to use checksums
026519a3b51141fada57e01cf1f9d3e39c7d78b7834400e955e12954abfdfa4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

0.20.0

2 release files

0.19.1

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.16.0

1 release file

0.14.0

2 release files

0.13.0

1 release file

This release

0.12.0 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page