Official Python SDK for EngageLab OTP.
Zero dependencies. Python 3.8+.
pip install engagelab-otpimportosfromengagelab_otpimportOTPClientotp=OTPClient(
os.environ["ENGAGELAB_DEV_KEY"],
os.environ["ENGAGELAB_DEV_SECRET"],
)
# Platform-generated OTP — easiest pathresult=otp.send("+6591234567", "your-template-id", {"name": "Alice"}, language="en")
check=otp.verify(result["message_id"], user_typed_code)
ifcheck["verified"]:
...| Mode | Method | When to use |
|---|---|---|
| Platform-generated | otp.send() | EngageLab generates and stores the code. You only call verify() later. |
| Caller-generated | otp.send_custom() | You generate the code yourself. EngageLab is just the carrier. |
# Platform-generatedr=otp.send("+6591234567", "tpl-id", {"name": "Alice"}, language="en")
v=otp.verify(r["message_id"], "123456")
# Caller-generatedotp.send_custom("+6591234567", "custom-tpl", {"code": "482910", "name": "Alice"})EngageLab signs callbacks with X-CALLBACK-ID (HMAC-SHA256). The SDK verifies signatures and parses events for you.
Whitelist source IPs in your firewall:
119.8.170.74,114.119.180.30
fromflaskimportFlaskfromengagelab_otpimportWebhookVerifier, MessageStatusEventapp=Flask(__name__)
verifier=WebhookVerifier(
username=os.environ["ENGAGELAB_WEBHOOK_USERNAME"],
secret=os.environ["ENGAGELAB_WEBHOOK_SECRET"],
)
defhandle(events):
foreinevents:
ifnotisinstance(e, MessageStatusEvent):
continueifnote.is_terminal:
continue# mid-flight, waitife.status=="delivered":
mark_delivered(e.message_id)
elife.status=="verified":
mark_verified(e.message_id)
app.add_url_rule(
"/webhook",
"engagelab_webhook",
verifier.flask_view(handle),
methods=["POST"],
)parse_events() returns instances of four dataclasses:
| Class | When | Key fields |
|---|---|---|
MessageStatusEvent | per-message lifecycle | message_id, status, is_terminal, current_send_channel, error_code |
NotificationEvent | account-level alert | event, data |
UplinkEvent | inbound user reply | data["from"], data["body"] |
SystemEvent | console action audit | event, data |
plan · target_valid · target_invalid
sent · sent_failed
delivered · delivered_failed
verified · verified_failed · verified_timeout
is_terminal == True for: delivered, delivered_failed, sent_failed, verified*, target_invalid.
fromengagelab_otpimportEngagelabErrortry:
otp.send("+6591234567", "tpl", {})
exceptEngagelabErrorase:
ife.retryable:
# HTTP 429/5xx, or API codes 1000/5001/5016# → exponential backoff
...
else:
# Permanent failure — fix call or notify userprint(e.code, e.http_status, str(e))See examples/ for runnable code:
01_send_and_verify.py— platform-generated OTP, full flow02_send_custom.py— caller-generated code, single + bulk03_webhook_flask.py— receive callbacks via Flask04_error_handling.py— retry strategy and error categorization
python tests/test_sdk.pyMIT