Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dr.green/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks

TL;DR. Dr Green has inbound webhook endpoints that its payment and KYC processors call into. As an external store builder, you are not the consumer of these — they’re between Dr Green and its third-party services. There is currently no outbound webhook system for stores: your store must poll the relevant endpoints to detect order, KYC, and commission state changes.

What this means for your store

If you’ve built integrations with Stripe, Shopify, or PayPal, you’re used to the pattern:
[Customer pays] → [Stripe receives event] → [Stripe POSTs to your /stripe-webhook] → [You react]
Dr Green doesn’t currently work that way for store builders. The pattern is:
[Customer pays via your store]
  → [Your store POSTs /dapp/orders to Dr Green]
  → [Dr Green admins approve internally] (asynchronous, hours to days)
  → [You poll GET /dapp/orders/:orderId] (every N minutes)
  → [You detect status change] → [You notify your customer]
This is functional but inefficient. We’ve flagged this as a roadmap gap to the Dr Green backend team. See § The polling pattern below for the workaround.

Inbound webhooks (between Dr Green and its processors — you can ignore these)

For completeness, the four webhook endpoints that exist on the Dr Green API:
EndpointCallerPurpose
POST /api/v1/kyc/webhookFirstAMLKYC/AML verification status updates from FirstAML back to Dr Green
POST /api/v1/payments/webhookCoinRemitterCrypto payment notifications
POST /api/v1/payments/fiat/webhookPayinnFiat payment notifications
POST /api/v1/payments/pgpay/webhookPGPayAlternate fiat payment gateway notifications
These endpoints are inbound to Dr Green — they receive POSTs from third-party services. The payload format is dictated by each upstream processor, not by Dr Green, and is not declared in the OpenAPI spec.
As a store builder, you don’t call these and they don’t call you. They’re listed here only so you understand the full surface — and so you don’t accidentally try to use them.

How the FirstAML KYC flow actually works

This is the most-asked-about flow, so worth spelling out clearly:
[Your store] ──POST /dapp/clients──▶ [Dr Green backend]

                                            ├─ (1) sends an email to the customer with KYC instructions

                                            └─ (2) fires an outbound webhook to FirstAML to open a case


                                                                                [FirstAML runs verification]
                                                                                with the customer (out-of-band —
                                                                                ID upload, liveness, AML checks)


                                            ┌──POST /api/v1/kyc/webhook──── [FirstAML]


                              [Dr Green updates client.isKYCVerified
                               and client.adminApproval]


[Your store] ◀──poll GET /dapp/clients/{id}──┘
Key takeaways for store builders:
  • Your store does not integrate with FirstAML. You don’t have FirstAML credentials, you don’t render their UI, you don’t receive their callbacks.
  • The outbound Dr Green → FirstAML webhook (step 2 above) is a separate, internal flow — not exposed in the public API surface.
  • The customer interacts with FirstAML directly (typically via a hosted verification portal linked from the email Dr Green sends).
  • All you do is POST /dapp/clients to start the flow, then poll GET /dapp/clients/{clientId} until isKYCVerified === true and adminApproval === 'VERIFIED'.
🔒 Inbound signing. The Dr Green backend should be verifying signatures on these inbound webhooks (FirstAML-specific signing scheme, plus IPN/HMAC from the payment processors). This was flagged for backend-team verification; status pending. Not your problem to solve, but worth being aware of when discussing platform security with Dr Green.

The polling pattern (what your store actually does)

Until outbound webhooks exist, build your store around polling. Here’s a battle-tested pattern.

What to poll

ConcernEndpointWhy
Order status change (the big one)GET /api/v1/dapp/orders/:orderIdDetect “approved”, “rejected”, “shipped”
KYC status change for a customerGET /api/v1/dapp/clients/:clientIdDetect verification completion
Recent orders across all customersGET /api/v1/dapp/orders/recentCheap heartbeat — surfaces anything new
Commission accrualGET /api/v1/dapp/commissions/summaryUpdate holder’s earnings widget
Strain catalogue freshnessGET /api/v1/dapp/strainsDetect new products and price changes

How to poll efficiently

Pattern: tiered polling intervals.
Resource freshnessPoll intervalJustification
Order status (active orders only)60sCustomers tolerate ~1min staleness on order status
Order status (orders >7 days old, not yet shipped)30 minLong-tail; rarely changes
KYC status (pending)5 minFirstAML verification timing varies by case complexity (minutes to hours)
Commission summary15 minUpdates aren’t time-critical
Strain catalogue1 hourCatalogue is stable; refresh on cache miss
Pattern: only poll active items. Don’t poll for orders that are already in a terminal state (shipped, cancelled, rejected). Maintain a list of “active” order IDs in your store DB; remove them once they reach a terminal status. Pattern: jittered polling. If your store has many holders, don’t poll all of them on the same cron-second — you’ll create thundering herds against the Dr Green API. Add ±10% random jitter to every interval.
# Minimal polling implementation
import asyncio, random

async def poll_active_orders(client, db):
    while True:
        active_order_ids = await db.get_active_order_ids()
        for oid in active_order_ids:
            try:
                resp = await client.get(f'/dapp/orders/{oid}')
                order = resp.data
                if order['status'] != await db.get_cached_status(oid):
                    await db.update_status(oid, order['status'])
                    await notify_customer(order)
                    if order['status'] in {'shipped', 'cancelled', 'rejected'}:
                        await db.deactivate(oid)
            except Exception as e:
                log.warning(f'Poll failed for {oid}: {e}')
        # 60s ± 10% jitter
        await asyncio.sleep(60 * (0.9 + 0.2 * random.random()))

What not to do

  • Don’t poll every order on every tick. Use the active-set pattern above.
  • Don’t poll faster than 1 req/sec per holder. The backend isn’t currently rate-limited but politeness avoids future enforcement pain.
  • Don’t poll without backoff on errors. A backend hiccup shouldn’t get amplified by your retry storm. Exponential backoff on consecutive failures.

Future outbound webhook design (when it ships)

When Dr Green ships outbound webhooks for store builders, the design we’d recommend (and have flagged) looks like:
  • HMAC-SHA256 signing of payload with a per-store secret you configure in the DAPP
  • Replay-protection timestamp in the signature payload
  • Standard event types: order.created, order.approved, order.rejected, order.shipped, client.kyc.completed, commission.accrued
  • At-least-once delivery with idempotency on the receiving side keyed by event ID
  • Retry policy of ~5 attempts over 24 hours
This isn’t the current state — it’s what we’d advocate for. If Dr Green ships outbound webhooks with a different design, this doc will be updated.

Summary

QuestionAnswer
Does Dr Green call my store on events?❌ Not currently.
Do I need to expose a webhook endpoint?❌ No.
How do I detect order status changes?Poll GET /api/v1/dapp/orders/:orderId.
What about FirstAML KYC results?Poll GET /api/v1/dapp/clients/:clientId. The KYC flow is handled entirely between Dr Green and FirstAML — your store doesn’t integrate with FirstAML.
Are the /payments/webhook endpoints for me?❌ No — those are between Dr Green and CoinRemitter / Payinn / PGPay.
When will outbound webhooks ship?🔒 Not on a published roadmap. Worth requesting from Dr Green.