Reevit Python SDK
The official Python SDK for Reevit — a unified payment orchestration platform for Africa.
Installation
pip install reevit==0.9.1
Quick Start
from reevit import Reevit
client = Reevit(api_key="pfk_live_xxx", org_id="org_123")
# Create a payment
try:
payment = client.payments.create_intent({
"amount": 5000, # 50.00 GHS
"currency": "GHS",
"method": "momo",
"country": "GH",
"customer_id": "cust_123",
"metadata": {
"order_id": "12345"
}
}, idempotency_key="order_12345")
print(f"Payment created: {payment['id']}")
except Exception as e:
print(f"Error: {e}")
# List payments
payments = client.payments.list()
print(payments)
Server-created checkout sessions
Create checkout sessions on your server, then pass session["session_secret"] to a browser SDK — @reevit/react, @reevit/vue, or @reevit/svelte — to render the checkout UI.
session = client.checkout_sessions.create(
{
"amount": 5000,
"currency": "GHS",
"method": "mobile_money",
"country": "GH",
},
idempotency_key="order_12345",
)
Idempotency
Pass idempotency_key to safely retry intent creation without duplicates.
payment = client.payments.create_intent(
{
"amount": 5000,
"currency": "GHS",
"method": "momo",
"country": "GH",
},
idempotency_key="order_12345",
)
Features
- Payments: Create intents, update intents, confirm, confirm intent, cancel, retry, refund, stats
- Connections: Manage PSP integrations, validation, labels, status, audit
- Subscriptions: Manage recurring billing lifecycle
- Fraud: Configure fraud rules
- Customers / Payment Links / Checkout Sessions / Webhooks / Routing Rules / Invoices: Additional backend services
org_id is supported directly on the client. Omitting it for authenticated requests still works for backward compatibility, but that mode is deprecated.
To fetch every connected PSP rather than one page:
connections = client.connections.list_all(mode="live", status="active")
page = client.connections.list_page(limit=50, offset=0)
Webhook Verification
Reevit sends webhooks to notify your application of payment events. Always verify webhook signatures.
Understanding Webhooks
There are two types of webhooks in Reevit:
-
Inbound Webhooks (PSP → Reevit): Webhooks from payment providers (Paystack, Flutterwave, etc.) to Reevit. Configure these in the PSP dashboard. Reevit handles them automatically.
-
Outbound Webhooks (Reevit → Your App): Webhooks from Reevit to your application. Configure in Reevit Dashboard and create a handler in your app.
Signature Format
- Header:
X-Reevit-Signature: sha256=<hex-signature> - Signature:
HMAC-SHA256(request_body, signing_secret)
Getting Your Signing Secret
- Go to Reevit Dashboard > Developers > Webhooks
- Configure your webhook endpoint URL
- Copy the signing secret (starts with
whsec_) - Set environment variable:
REEVIT_WEBHOOK_SECRET=whsec_xxx...
Flask Webhook Handler
The SDK ships a constant-time verifier — verify_webhook_signature(payload, signature, secret) — so you do not have to reimplement HMAC. Pass the raw request body (not parsed-and-reserialized JSON), the X-Reevit-Signature header, and your signing secret.
import os
import logging
from dataclasses import dataclass
from typing import Optional, Dict, Any
from flask import Flask, request, jsonify
from reevit import verify_webhook_signature
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class PaymentData:
id: str
status: str
amount: int
currency: str
provider: str
customer_id: Optional[str] = None
metadata: Optional[Dict[str, str]] = None
@dataclass
class SubscriptionData:
id: str
customer_id: str
plan_id: str
status: str
amount: int
currency: str
interval: str
next_renewal_at: Optional[str] = None
@app.route('/webhooks/reevit', methods=['POST'])
def webhook():
payload = request.get_data() # raw bytes — do not re-serialize
signature = request.headers.get('X-Reevit-Signature', '')
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
# Verify signature (required in production)
if not verify_webhook_signature(payload, signature, secret):
logger.warning('[Webhook] Invalid signature')
return jsonify({'error': 'Invalid signature'}), 401
event = request.get_json()
event_type = event.get('type')
event_id = event.get('id')
logger.info(f'[Webhook] Received: {event_type} ({event_id})')
# Handle different event types
if event_type == 'reevit.webhook.test':
logger.info(f'[Webhook] Test received: {event.get("message")}')
# Payment events
elif event_type == 'payment.succeeded':
data = PaymentData(**event.get('data', {}))
handle_payment_succeeded(data)
elif event_type == 'payment.failed':
data = PaymentData(**event.get('data', {}))
handle_payment_failed(data)
elif event_type == 'payment.refunded':
data = PaymentData(**event.get('data', {}))
handle_payment_refunded(data)
elif event_type == 'payment.pending':
data = PaymentData(**event.get('data', {}))
logger.info(f'[Webhook] Payment pending: {data.id}')
# Subscription events
elif event_type == 'subscription.created':
data = SubscriptionData(**event.get('data', {}))
handle_subscription_created(data)
elif event_type == 'subscription.renewed':
data = SubscriptionData(**event.get('data', {}))
handle_subscription_renewed(data)
elif event_type == 'subscription.canceled':
data = SubscriptionData(**event.get('data', {}))
handle_subscription_canceled(data)
else:
logger.info(f'[Webhook] Unhandled event: {event_type}')
return jsonify({'received': True})
# Payment handlers
def handle_payment_succeeded(data: PaymentData):
order_id = data.metadata.get('order_id') if data.metadata else None
logger.info(f'[Webhook] Payment succeeded: {data.id} for order {order_id}')
# TODO: Implement your business logic
# - Update order status to "paid"
# - Send confirmation email to customer
# - Trigger fulfillment process
def handle_payment_failed(data: PaymentData):
logger.info(f'[Webhook] Payment failed: {data.id}')
# TODO: Implement your business logic
# - Update order status to "payment_failed"
# - Send notification to customer
# - Allow retry
def handle_payment_refunded(data: PaymentData):
order_id = data.metadata.get('order_id') if data.metadata else None
logger.info(f'[Webhook] Payment refunded: {data.id} for order {order_id}')
# TODO: Implement your business logic
# - Update order status to "refunded"
# - Restore inventory if applicable
# Subscription handlers
def handle_subscription_created(data: SubscriptionData):
logger.info(f'[Webhook] Subscription created: {data.id} for customer {data.customer_id}')
# TODO: Implement your business logic
# - Grant access to subscription features
# - Send welcome email
def handle_subscription_renewed(data: SubscriptionData):
logger.info(f'[Webhook] Subscription renewed: {data.id}')
# TODO: Implement your business logic
# - Extend access period
# - Send renewal confirmation
def handle_subscription_canceled(data: SubscriptionData):
logger.info(f'[Webhook] Subscription canceled: {data.id}')
# TODO: Implement your business logic
# - Revoke access at end of billing period
# - Send cancellation confirmation
if __name__ == '__main__':
app.run(port=8080)
Django Webhook Handler
# views.py
import json
import os
import logging
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from reevit import verify_webhook_signature
logger = logging.getLogger(__name__)
@csrf_exempt
@require_POST
def reevit_webhook(request):
payload = request.body # raw bytes — do not re-serialize
signature = request.headers.get('X-Reevit-Signature', '')
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
if not verify_webhook_signature(payload, signature, secret):
return JsonResponse({'error': 'Invalid signature'}, status=401)
event = json.loads(payload)
event_type = event.get('type')
logger.info(f'[Webhook] Received: {event_type}')
if event_type == 'payment.succeeded':
data = event.get('data', {})
order_id = data.get('metadata', {}).get('order_id')
# Fulfill order, send confirmation email
logger.info(f'Payment succeeded for order {order_id}')
elif event_type == 'payment.failed':
# Notify customer, allow retry
pass
elif event_type == 'subscription.renewed':
# Extend access
pass
elif event_type == 'subscription.canceled':
# Revoke access
pass
return JsonResponse({'received': True})
FastAPI Webhook Handler
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict
import os
import logging
from reevit import verify_webhook_signature
app = FastAPI()
logger = logging.getLogger(__name__)
class PaymentData(BaseModel):
id: str
status: str
amount: int
currency: str
provider: str
customer_id: Optional[str] = None
metadata: Optional[Dict[str, str]] = None
class SubscriptionData(BaseModel):
id: str
customer_id: str
plan_id: str
status: str
amount: int
currency: str
interval: str
next_renewal_at: Optional[str] = None
@app.post('/webhooks/reevit')
async def webhook(request: Request):
payload = await request.body() # raw bytes — do not re-serialize
signature = request.headers.get('X-Reevit-Signature', '')
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
if not verify_webhook_signature(payload, signature, secret):
raise HTTPException(status_code=401, detail='Invalid signature')
event = await request.json()
event_type = event.get('type')
logger.info(f'[Webhook] Received: {event_type}')
# Payment events
if event_type == 'payment.succeeded':
data = PaymentData(**event.get('data', {}))
order_id = data.metadata.get('order_id') if data.metadata else None
logger.info(f'Payment succeeded: {data.id} for order {order_id}')
# Fulfill order, send confirmation email
elif event_type == 'payment.failed':
# Notify customer, allow retry
pass
# Subscription events
elif event_type == 'subscription.renewed':
data = SubscriptionData(**event.get('data', {}))
logger.info(f'Subscription renewed: {data.id}')
# Extend access
elif event_type == 'subscription.canceled':
data = SubscriptionData(**event.get('data', {}))
logger.info(f'Subscription canceled: {data.id}')
# Revoke access
return {'received': True}
Supported PSPs
| Provider | Countries | Payment Methods |
|---|---|---|
| Paystack | NG, GH, ZA, KE | Card, Mobile Money, Bank Transfer |
| Flutterwave | NG, GH, KE, ZA + | Card, Mobile Money, Bank Transfer |
| Hubtel | GH | Mobile Money |
| Stripe | Global (50+) | Card, Apple Pay, Google Pay |
| Monnify | NG | Card, Bank Transfer, USSD |
| M-Pesa | KE, TZ | Mobile Money (STK Push) |
Release Notes
v0.9.1
- Added
verify_webhook_signature/sign_webhook_payloadhelpers (constant-time HMAC-SHA256 verification of theX-Reevit-Signatureheader) - Version is now sourced from
reevit._versionand sent as theX-Reevit-Client-Versionheader
v0.9.0
- Added server-created checkout sessions
- Version alignment across all Reevit SDKs
- Updated documentation and webhook examples
- Added support for Apple Pay and Google Pay
- Updated supported PSPs and payment methods documentation
Environment Variables
export REEVIT_API_KEY=pfk_live_xxx
export REEVIT_ORG_ID=org_xxx
export REEVIT_WEBHOOK_SECRET=whsec_xxx # Get from Dashboard > Developers > Webhooks
Support
- Documentation: https://docs.reevit.io
- GitHub Issues: https://github.com/Reevit-Platform/backend/issues
- Email: support@reevit.io
License
MIT License - see LICENSE for details.
Release files for reevit 0.10.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| reevit-0.10.1.tar.gz | 18.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reevit-0.10.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.3 kB
Release files / reevit-0.10.1.tar.gz
| Download URL | reevit-0.10.1.tar.gz |
|---|---|
| Size | 18.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1739fa3540ce24d808d4f6c9c9657001bf16499ab77330b51d0f759da730a126
|
|
BLAKE2b-256 checksum How to use checksums |
56b559f51915f80415006370c590296424911ab9f4ee1ecb63424509224550f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 14, 2026.
Transparency logRelease files / reevit-0.10.1-py3-none-any.whl
| Download URL | reevit-0.10.1-py3-none-any.whl |
|---|---|
| Size | 17.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83e2216924ce413e78c0174a26e242b65ab0a32a1901c830b12cb3d47746d8d2
|
|
BLAKE2b-256 checksum How to use checksums |
3493823a22395b962f6f28b85951e122dc11e42aa459d34fbd40adb9443ceff4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 14, 2026.
Transparency log