Easy Labs
SDKsPython

Webhooks

Verify and consume webhooks from the Easy API.

Webhooks let your server react to events that happen asynchronously on Easy Labs — payments completing, subscriptions renewing, invoices finalizing, disputes opening — without polling for changes. Easy Labs delivers each event as a signed POST to a URL you register with client.webhooks_management.register(...). The SDK ships a small, framework-agnostic verifier that turns the raw request into a typed WebhookEvent.

The webhook surface has two halves:

APIPurpose
easylabs.Webhooks.construct_event(...)Verify a signature + parse the event payload.
client.webhooks_management.*Register / list / delete endpoints, list deliveries.

Endpoint management lives on its own Webhook Endpoints page; this page covers verification.

Verifying signatures

import os
from easylabs import Webhooks, InvalidRequestError

SECRET = os.environ["EASY_WEBHOOK_SECRET"]  # returned once on register()


def handle(request):
    try:
        event = Webhooks.construct_event(
            payload=request.body,                                  # bytes preferred
            signature=request.headers.get("x-easy-webhook-signature", ""),
            secret=SECRET,
        )
    except InvalidRequestError as e:
        # Signature missing / malformed / mismatched, or body wasn't JSON.
        return ("invalid signature", 400)

    if event.type == "payment.created":
        # event.data carries the resource payload (dict)
        ...
    elif event.type == "subscription.trial_will_end":
        ...

    return ("", 204)

Notes:

  • Pass the raw request body, not a re-serialized JSON string. The signature is computed over the exact bytes Easy Labs sent — pretty- printing or re-encoding will fail verification.

  • payload= accepts bytes (preferred) or str.

  • Webhooks.construct_event raises easylabs.InvalidRequestError (HTTP 400) on every verification failure. Inspect e.code to distinguish cases:

    e.codeMeaning
    WEBHOOK_SIGNATURE_MISSINGThe x-easy-webhook-signature header was empty or absent.
    WEBHOOK_SIGNATURE_FORMAT_INVALIDHeader didn't start with sha256= or wasn't valid hex.
    WEBHOOK_SIGNATURE_MISMATCHSignature didn't match what we computed from the body.
    WEBHOOK_BODY_INVALID_JSONBody verified but failed to parse as JSON.

Comparison uses hmac.compare_digest, so verification is constant-time.

Replay protection

The SDK verifies the HMAC signature over the body. Easy Labs will re-deliver the same event with the same signature on retries, which is exactly what makes webhooks resilient — but it also means a leaked payload could in principle be re-played by an attacker who learns the URL.

Two recommendations:

  • Idempotency on your side. Treat event.id as a primary key. Skip processing if you've already seen it.

    if WebhookEventLog.exists(event.id):
        return ("", 204)
    WebhookEventLog.record(event.id, event.type)
  • Tight network exposure. Restrict the receive endpoint to TLS, authenticate the source IP / proxy, and rotate the signing secret if you suspect compromise (webhooks_management.update).

Common event types

The full list is exported as easylabs.EVENT_TYPES:

from easylabs import EVENT_TYPES
print(EVENT_TYPES)

A representative subset:

EventFired when…
payment.created / payment.updatedA Transfer is created or transitions state.
refund.created / refund.updatedA reversal is created or transitions state.
authorization.created / .updated / .voidedAuth-and-capture lifecycle changes.
subscription.created / .updated / .deletedSubscription lifecycle.
subscription.paused / .resumedPause collection toggles.
subscription.trial_will_end3 days before a trial ends.
subscription.pending_update_applied / .pending_update_expiredScheduled changes resolve.
invoice.created / .finalized / .paid / .payment_failed / .upcoming / .voided / .marked_uncollectibleInvoice lifecycle.
revenue_recovery.action_completedA dunning step ran.
coupon.* / promotion_code.*Discount config changes.
identity.created / .updatedCustomer / identity changes.
settlement.createdA settlement was issued.
dispute.created / .updatedChargeback / dispute lifecycle.
checkout.session.completedA hosted / embedded checkout finished.
checkout.session.crypto_confirmedA crypto checkout payment was confirmed on-chain.
test.webhookSent when you click "Send test event" in the dashboard.

The full payload for each event is documented in the Easy Labs API reference.

On this page