02 - End-to-End Integration Flow


The definitive path from first register to live in the Super App, plus
the two runtime systems every partner touches: orders (fulfillment) and
signed webhooks (event delivery).

See also:


1. Full lifecycle

flowchart TD
    R[POST /register] --> OTP[Verify OTP]
    OTP --> Login[POST /auth/login + verify-otp]
    Login --> Company[PATCH /company + upload docs<br/>CR / TAX / REP_ID]
    Company --> Ready{GET /company/readiness<br/>canSubmitVerification?}
    Ready -->|no| Company
    Ready -->|yes| Submit[POST /company/submit]
    Submit --> Ver[Admin review<br/>UNDER_REVIEW / APPROVED]
    Ver -->|INFO_REQUIRED / REJECTED| Company
    Ver -->|APPROVED| App[POST /applications<br/>integrationModel EMBEDDED]
    App --> Listing[PATCH /listing + offers + panels]
    Listing --> ListSub[POST /listing/submit]
    ListSub --> AdminList[Admin decision<br/>APPROVED / CHANGES_REQUESTED]
    AdminList -->|CHANGES_REQUESTED| Listing
    AdminList -->|APPROVED / PUBLISHED| Live[Customers buy in Super App<br/>POST /marketplace/purchase]
    Live --> WH[Partner receives webhooks]

Phase gates


2. Stage-by-stage walkthrough

Stage A — Onboarding (Days 0–1)

Readiness response:

{
  "organizationStatus": "ONBOARDING",
  "verificationStatus": "DRAFT",
  "profileIncomplete": true,
  "missingFields": ["bankName: is required", "iban: is required"],
  "missingDocuments": ["REP_ID"],
  "canSubmitVerification": false,
  "canCreateApplication": false,
  "canPublishListing": false
}

Status enums — organization & verification

EventOrg statusVerification status
RegisterONBOARDINGDRAFT
Submit KYCPENDING_VERIFICATIONSUBMITTED
Admin reviewPENDING_VERIFICATIONUNDER_REVIEW
Admin approveAPPROVEDAPPROVED
Admin rejectREJECTEDREJECTED
Admin suspendSUSPENDEDSUSPENDED

Once org = APPROVED you can create an application.

Stage B — Build the service (Days 2–5)

  1. POST /developer-portal/applications → DRAFT application
    (integrationModel: "EMBEDDED").
  2. PATCH /developer-portal/applications/:id — set websiteUrl,
    redirectUrls, allowedWebOrigins, partnerApiBaseUrl, supportContact.
  3. POST .../credentials/sandbox/generate — raw key shown once.
  4. PATCH .../listing then add offers/panels.
  5. POST .../listing/submit → listing SUBMITTED.

Offers payload

{
  "titleEn": "Silver top-up",
  "titleAr": "تعبئة فضية",
  "descriptionEn": "Instant digital top-up",
  "design": "banner",
  "linkType": "offer",
  "url": "https://partner.acme.sa/buy/silver",
  "price": "49.99",
  "originalPrice": "79.99",
  "currency": "SAR",
  "inStock": true,
  "perUserLimit": 5,
  "badgeLabel": "Best seller",
  "badgeColor": "#E8553C",
  "fulfillmentMode": "INSTANT"
}

Offer price is a decimal string (≤ 7 integer + 2 fraction digits); titles are
capped at 200 chars; a listing holds at most 20 offer cards. For composing
panels, card styles, and layout recipes, see
Panels & storefront.


Fulfillment modes

flowchart LR
    Charge[Card charged] --> Mode{fulfillmentMode}
    Mode -->|INSTANT| Done[Order COMPLETED immediately]
    Mode -->|PARTNER_CONFIRMATION| Pending[Order PENDING_PARTNER_CONFIRM]
    Pending -->|partner confirm->start->complete| Done
  • INSTANT — order completes immediately (PAYMENT_AUTHORIZED → COMPLETED).
  • PARTNER_CONFIRMATION — you drive fulfillment via
    POST .../orders/:orderId/{confirm|start|complete|cancel}
    (PAYMENT_AUTHORIZED → PENDING_PARTNER_CONFIRM → CONFIRMED → IN_PROGRESS → COMPLETED).

Stage C — Go live (Day 6)

  1. Ensure your webhook endpoint is configured and tested in sandbox.
  2. Listing is PUBLISHED → customers can purchase in the Super App.
  3. Monitor order webhooks and fulfill via confirmation endpoints if needed.

3. The purchase moment (customer side)

sequenceDiagram
    autonumber
    participant C as Customer (Super App)
    participant M as DigetPay /v1/marketplace
    participant G as Card gateway
    participant P as Partner endpoint

    C->>M: POST /marketplace/purchase { offerId, savedMethodId, pin }
    M->>G: recurringCharge (vault token / saved method)
    M-->>C: 201 order { status, fulfillmentMode, fulfillment }
    M-->>P: webhook payment.created
    par INSTANT path
        M-->>P: webhook order.completed
        M-->>C: fulfillment (deeplink or webview+launch token)
    and PARTNER_CONFIRMATION path
        M-->>P: webhook order.confirmation_required
        P-->>M: POST .../orders/:id/confirm
        P-->>M: POST .../orders/:id/start
        P-->>M: POST .../orders/:id/complete
        M-->>C: fulfillment becomes available
    end
  • Purchases are idempotent via the x-idempotency-key header — a replayed
    key returns the original order instead of charging twice.
  • If the wallet PIN is missing, the API returns 401 PIN_REQUIRED so the app
    shows the PIN sheet and retries with pin.

Settlement — the developer's net

$$ S_{net} = price_{unit} \times qty - fee_{processing} $$

where S_{net} is the amount settled to your payout account, qty is paid
units, and fee_{processing} is DigetPay's processing fee. Settlement events
arrive as webhook settlement.completed.

Order status enum

StatusMeaning
CREATEDDraft order row created
PENDING_PAYMENTCharge initiated against the card
PAYMENT_AUTHORIZEDCharge captured, awaiting fulfillment
PENDING_PARTNER_CONFIRMWaiting for partner confirm
CONFIRMEDPartner acknowledged the order
IN_PROGRESSPartner is fulfilling (start)
COMPLETEDFulfilled (complete) / instant
CANCELLEDPartner cancelled
FAILEDPayment failed / expired
REFUND_PENDING / REFUNDEDRefund underway / completed

4. Orders endpoints

MethodEndpointPurpose
GET/developer-portal/applications/:id/ordersMarketplace orders for your app
GET/developer-portal/applications/:id/transactionsSame data as a sales ledger
POST/developer-portal/applications/:id/orders/:orderId/:actionAdvance an order

4.1 List application orders

curl -X GET \
  "https://fin-api.digetpay.com/v1/developer-portal/applications/42/orders?page=1&pageSize=25&status=COMPLETED" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Response 200 — { items, meta }

{
  "id": "1024",
  "status": "COMPLETED",
  "fulfillmentStatus": "COMPLETED",
  "fulfillmentMode": "INSTANT",
  "offer": { "offerId": "12", "titleEn": "Silver top-up", "price": "49.99", "currency": "SAR", "quantity": 1 },
  "fulfillment": { "type": "deeplink", "url": "https://partner.acme.sa/redeem/7f3a…", "token": null, "allowedOrigins": [] },
  "createdAt": "2026-08-18T10:04:12.000Z"
}

4.2 Advance an order (partner-confirmation models)

curl -X POST \
  "https://fin-api.digetpay.com/v1/developer-portal/applications/42/orders/1024/complete" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
ActionMeaningAllowed from
confirmAccept the orderPENDING_PARTNER_CONFIRM
startBegin fulfillmentCONFIRMED
completeFinish fulfillmentIN_PROGRESS
cancelCancel (money refund flows separately)PENDING_PARTNER_CONFIRM
❌

cancel does not itself move money back. Refunds are driven by the refund
workflow / gateway and surface via the payment.refunded webhook.


Order actions are validated transitions — an illegal transition returns 400.
Take the order status from the webhook, not from a local cache.



5. Webhook endpoints

MethodEndpointPurpose
GET.../applications/:appId/webhooks/catalogRead the event catalog
GET.../applications/:appId/webhooksList endpoints
POST.../applications/:appId/webhooksCreate an endpoint
PATCH.../applications/:appId/webhooks/:endpointIdUpdate URL/events/active
DELETE.../applications/:appId/webhooks/:endpointIdRemove an endpoint
POST.../applications/:appId/webhooks/:endpointId/testSend a signed test delivery
GET.../applications/:appId/webhooks/deliveriesDelivery log for an app

5.1 Create an endpoint

{
  "environment": "SANDBOX",
  "url": "https://api.acme.sa/webhooks/digetpay",
  "events": ["payment.completed", "order.completed"],
  "retryLimit": 3
}
  • environment ∈ SANDBOX | PRODUCTION
  • url must be https
  • events ≤ 20, chosen from the catalog below
  • retryLimit ∈ 1–5 (default 3)

5.2 Event catalog

EventRaised when
application.approvedCompany verification approved
application.rejectedVerification rejected
payment.createdCard charge initiated
payment.completedCharge captured
payment.failedCharge declined / failed
payment.refundedRefund finalized
order.confirmation_requiredWaiting for partner confirm
order.confirmedPartner confirmed
order.in_progressPartner started fulfillment
order.completedFulfillment complete (or INSTANT)
order.cancelledOrder cancelled
order.expiredOrder auto-expired
order.failedOrder failed
service.completedEnd-service milestone completed
settlement.completedA settlement batch was paid out
webhook.connection_testEndpoint /test button (not subscribable)

6. Delivery envelope & signatures

Every delivery is a POST (10 s timeout) with JSON body:

{
  "event": "payment.completed",
  "environment": "SANDBOX",
  "applicationId": "42",
  "createdAt": "2026-08-18T10:04:12.000Z",
  "data": { "orderId": "1024", "amount": "49.99", "currency": "SAR" }
}

The outer fields (event, environment, applicationId, createdAt) are
always present; the business object lives in data. Prefer the
X-DigetPay-Event header for routing and the event body field for
verification.

Signature construction

$$ signature = \text{HMAC-SHA256}(webhookSecret,\ timestamp_{sec}\ .\ rawBody) $$

Headers every delivery carry:

HeaderExample value
Content-Typeapplication/json
User-AgentDigetPay-Webhooks/1.0
X-DigetPay-Timestamp1784544252 (Unix seconds)
X-DigetPay-Signaturesha256=… (hex HMAC over {ts}.{rawBody})
X-DigetPay-Eventpayment.completed
# Recompute the expected signature on your side
TS="1784544252"
BODY='{"event":"payment.completed","environment":"SANDBOX",...}'
EXPECTED="sha256=$(printf '%s.%s' "$TS" "$BODY" \
  | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')"

[ "$EXPECTED" = "$DIGETPAY_SIGNATURE" ] && echo "verified" || echo "reject"

Verify the signature and check X-DigetPay-Timestamp is fresh (e.g. within
±5 minutes) before trusting any payload.


The webhook is signed with the application's webhook secret, not the API
key. Rotate it via POST .../credentials/{env}/rotate-webhook-secret.


Retry & backoff policy

AttemptWait before nextTrigger
Any—success = HTTP 2xx
1st~250 msnetwork error, 429, 5xx
2nd~500 mssame
3rd~1 ssame
4th~2 ssame
laststopfailure logged to deliveries
  • retryLimit caps attempts at 1–5 (default 3).
  • Non-retryable 4xx responses (e.g. 400) are not retried.
  • Every attempt is recorded in the deliveries log with httpStatus, attempts,
    lastError, and success.

Endpoint test

POST .../webhooks/:endpointId/test sends a one-shot webhook.connection_test
delivery with data.message = "DigetPay webhook connection test" (no retries)
and returns { httpStatus, success, errorMessage }.

Sample payloads


7. Deliveries log

GET .../webhooks/deliveries?page=1&pageSize=25 returns delivery attempts:

FieldTypeMeaning
endpointIdstringWebhook endpoint that received it
eventTypestringDelivered event
httpStatusnumber?Last HTTP status (null = network error)
successbooleanWhether the last attempt was 2xx
attemptsintegerHow many attempts were made
lastErrorstring?Last failure reason

Use the deliveries log to debug silent misses — check that events[]
subscription, active, and retryLimit all look right first.



8. Testing flow (staging walkthrough)

  • Register → login → dashboard shows ONBOARDING
  • Company profile + CR/TAX/REP uploads → readiness clears → submit SUBMITTED
  • Admin APPROVED → create application (gate removed)
  • Sandbox credentials generated (raw key captured once)
  • Webhook endpoint created + /test returns signed delivery
  • Listing submitted → admin APPROVED/PUBLISHED
  • Customer purchase → payment.completed + order.completed

9. Go-live checklist

  • Endpoint created for PRODUCTION with the full event set you rely on
  • X-DigetPay-Signature verified on staging
  • Timestamp freshness (+/-5 min) enforced
  • Retry handling + deliveries monitoring in place
  • payment.refunded handled (money can always come back)
  • Order PARTNER_CONFIRMATION actions tested through complete

Orders + signed webhooks cover the runtime contract. Integration is complete
once your endpoint verifies signatures and your fulfillment actions move orders
to COMPLETED.


Did this page help you?