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.
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reevit-0.9.1.tar.gz.
File metadata
- Download URL: reevit-0.9.1.tar.gz
- Upload date:
- Size: 14.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c1c007fef3ddfb9b615edd8baebdef0a20e801fb1772af421128da32261014a
|
|
| MD5 |
ff90c828a869b4d3b4799699c66c2773
|
|
| BLAKE2b-256 |
e2e461015e7dcc89a98b6bbb7593d80664f12e68ad2a3658d4517c4bcab95f3a
|
Provenance
The following attestation bundles were made for reevit-0.9.1.tar.gz:
Publisher:
publish.yml on Reevit-Platform/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reevit-0.9.1.tar.gz -
Subject digest:
1c1c007fef3ddfb9b615edd8baebdef0a20e801fb1772af421128da32261014a - Sigstore transparency entry: 2163573576
- Sigstore integration time:
-
Permalink:
Reevit-Platform/python-sdk@c95e20c4fcb9d7911a4f05201feed990f12ec6de -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Reevit-Platform
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
self-hosted -
Publication workflow:
publish.yml@c95e20c4fcb9d7911a4f05201feed990f12ec6de -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file reevit-0.9.1-py3-none-any.whl.
File metadata
- Download URL: reevit-0.9.1-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8810843781c2c348c9cedda52af0e118108ee92b002c6761adfc8e5ae3336c87
|
|
| MD5 |
7766930cd60ebe2778747ac14d4da11c
|
|
| BLAKE2b-256 |
d4c698e94051a721d16ed9aa6b7405af435f9bbe89acf6ee42503fea507c3e5e
|
Provenance
The following attestation bundles were made for reevit-0.9.1-py3-none-any.whl:
Publisher:
publish.yml on Reevit-Platform/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reevit-0.9.1-py3-none-any.whl -
Subject digest:
8810843781c2c348c9cedda52af0e118108ee92b002c6761adfc8e5ae3336c87 - Sigstore transparency entry: 2163573597
- Sigstore integration time:
-
Permalink:
Reevit-Platform/python-sdk@c95e20c4fcb9d7911a4f05201feed990f12ec6de -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Reevit-Platform
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
self-hosted -
Publication workflow:
publish.yml@c95e20c4fcb9d7911a4f05201feed990f12ec6de -
Trigger Event:
workflow_dispatch
-
Statement type: