Skip to content

Postbacks to Merchants

After a lender reaches a decision on a financing request, WeGetFinancing sends an HTTP POST to the merchant’s configured postback URL. This is the primary mechanism for order fulfilment.

Merchant postback flow

Create the order only after receiving an approved postback.


Configure the postback URL in one of two ways:

  1. Per-merchant default — set in the partner portal under Integration → API Integration.
  2. Per-request override — pass postback_url in the Create Request body.

If neither is set, the postback is silently discarded (deleted from the queue without sending).


The format depends on the version field sent in the original Create Request.

Content-Type: application/json

Approval:

{
"version": "1.9",
"request_token": "df0c3186b69be8aad35ff837a841d347",
"merchant_transaction_id": "ORDER-123",
"updates": {
"status": "approved"
}
}

Pre-approval:

{
"version": "1.9",
"request_token": "df0c3186b69be8aad35ff837a841d347",
"merchant_transaction_id": "ORDER-123",
"updates": {
"status": "preapproved"
}
}

Rejection:

{
"version": "1.9",
"request_token": "df0c3186b69be8aad35ff837a841d347",
"updates": {
"status": "rejected"
}
}

Refund:

{
"version": "1.9",
"request_token": "df0c3186b69be8aad35ff837a841d347",
"updates": {
"status": "refund",
"amount": "1200.00"
}
}
FieldTypeDescription
versionstringPostback dialect ("1.9")
request_tokenstringThe inv_id returned by the original create-request (the unique ID of the merchant request)
merchant_transaction_idstringYour original merchant_transaction_id, if provided (stored as cust_id_ext internally)
updates.statusstringDecision status — see Status Values
updates.amountstringRefund amount — only present when status is "refund"

The v1.9 body is constructed by PostBack.to_body() which maps the internal fields:

  • function: "transact" + method: "purchase" + inv_status: "Auth""approved"
  • function: "transact" + method: "purchase" + inv_status: "AuthOnly""preapproved"
  • function: "transact" + method: "void""rejected"
  • function: "refund""refund" with amount

Sent for merchants with version: "1.0" through "1.8".

Content-Type: application/json

The body contains the raw params dict with function, method, crl_id, and version fields added:

{
"version": "1.0",
"inv_id": "df0c3186b69be8aad35ff837a841d347",
"inv_status": "Auth",
"function": "transact",
"method": "purchase",
"crl_id": "some-uuid"
}
FieldTypeDescription
inv_idstringUnique request identifier (same as request_token in v1.9)
inv_statusstring"Auth" (approved) or "AuthOnly" (pre-approved)
functionstring"transact" for loan decisions
methodstring"purchase" (approval) or "void" (rejection)
crl_idstringUUID correlation ID (unique per postback attempt)

Content-Type: application/x-www-form-urlencoded

  • v0.2 — uses method for the function name and type for purchase/void.
  • v0.3+ — uses function and method as field names (same as v1.0 JSON but URL-encoded).

These formats are deprecated. Migrate to version: "1.9".


v1.9 statusv1.0 inv_statusInternal methodMeaning
approvedAuthpurchaseLoan fully approved — safe to create the order
preapprovedAuthOnlypurchasePre-approval only; customer must still complete the process
rejectedvoidLoan rejected or voided
refundLoan refunded; updates.amount contains the refund amount

Every outbound postback includes two integrity headers:

HeaderDescription
x-timestampUnix timestamp (seconds) at time of sending
x-signatureSHA-256 hex digest — see construction below
x-signature = SHA256( x-timestamp + key_id + raw_body + key_secret )

Where:

  • x-timestamp — the value of the x-timestamp header (string, not integer)
  • key_id — merchant’s dhd_username (the API username from account settings)
  • raw_body — the exact request body bytes as sent — do not parse/re-serialize
  • key_secret — merchant’s dhd_password (the API password from account settings)
import hashlib
import hmac
def verify_postback(headers: dict, raw_body: str, key_id: str, key_secret: str) -> bool:
timestamp = headers.get("x-timestamp", "")
received_sig = headers.get("x-signature", "")
expected_sig = hashlib.sha256(
(timestamp + key_id + raw_body + key_secret).encode()
).hexdigest()
# constant-time comparison
return hmac.compare_digest(expected_sig, received_sig)

The postback system maintains a state machine for each merchant account and processes postbacks one at a time.

Each postback carries its own backoff schedule:

ParameterValue
Initial retry delay60 seconds
Max delay between retries72 hours (259,200 seconds)
Backoff strategyExponential doubling (last_delay * 2, capped at max)
Maximum postback age7 days (604,800 seconds)

After 7 days without a successful delivery, the postback is permanently abandoned.

An account-level backoff is also applied when a server error occurs:

ParameterValueSource
Initial account delay113 secondsDEFAULT_MERCHANT_RETRY
Max account delay~3.7 hours (13,331 seconds)DEFAULT_MAX_RETRY_DELAY

Account-level backoff pauses all postbacks for that merchant, not just the failed one. It resets when a postback succeeds.

Certain postback types are never retried after the first delivery failure:

ConditionReason
inv_status: "AuthOnly" (pre-approved)Prevents race conditions if the loan gets purchased while retrying
method: "void" (rejected)Rejection notification is informational; retrying is unnecessary

Only approved postbacks (method: "purchase", inv_status: "Auth") receive the full exponential retry treatment.

The postback agent adds a 300-second penalty to postbacks created on a different database node, promoting locality — the same node that created the postback handles its delivery.


VersionRequired response
v1.9Any HTTP status < 300
v < 1.9HTTP 200 with body OK (exact string)

A non-conforming response triggers a retry (subject to the rules above).


All lenders support postbacks except Payd and Leap Theory.


WeGetFinancing exposes a read API on the merchant resource for querying pending postback documents.

GET /merchant/{merchant_id}/postbacks
GET /merchant/{merchant_id}/postbacks/{crl_id}
POST /merchant/{merchant_id}/postbacks
GET /merchant/{merchant_id}/postbacks

Returns a list of PostBack documents linked to the merchant account. Each item is identified by its crl_id (correlation UUID).

Response: Array of postback objects. Each item:

FieldTypeDescription
bodystringSerialised postback payload that was (or will be) sent to the merchant

Items are keyed by crl_id in the collection; navigate to the individual endpoint to fetch a specific one.

Terminal window
curl -u "$USERNAME:$PASSWORD" \
-H "Accept: application/json" \
"https://api.sandbox.wegetfinancing.com/merchant/1234/postbacks"
GET /merchant/{merchant_id}/postbacks/{crl_id}

Returns a single postback record by its correlation UUID.

Response:

{
"body": "{\"version\":\"1.9\",\"request_token\":\"df0c...\",\"updates\":{\"status\":\"approved\"}}"
}
POST /merchant/{merchant_id}/postbacks

Manually triggers a postback for an existing loan. Only enabled in the staging/sandbox dataspace (action.enabled(call.model_call('is_staging'))). Attempting this on production will return an error.

FieldTypeRequiredDescription
typeenumYespurchase (success) or void (rejection/end)
inv_statusenumYesAuthOnly (pre-approved) or Auth (funded)
inv_idstringYesThe loan inv_id to postback. Options are dynamically populated from existing merchant loans.

Response: HTTP 201 Created on success.

Terminal window
curl -u "$USERNAME:$PASSWORD" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-X POST \
"https://api.sandbox.wegetfinancing.com/merchant/1234/postbacks" \
-d '{"type":"purchase","inv_status":"Auth","inv_id":"df0c3186b69be8aad35ff837a841d347"}'

To simulate a v1.9 postback from your local environment for integration testing:

Terminal window
TIMESTAMP=$(date +%s)
KEY_ID="your_dhd_username"
KEY_SECRET="your_dhd_password"
BODY='{"version":"1.9","request_token":"df0c3186b69be8aad35ff837a841d347","updates":{"status":"approved"}}'
SIGNATURE=$(printf "%s%s%s%s" "$TIMESTAMP" "$KEY_ID" "$BODY" "$KEY_SECRET" | sha256sum | awk '{print $1}')
curl -X POST https://your-shop.com/api/postback \
-H "Content-Type: application/json" \
-H "x-timestamp: $TIMESTAMP" \
-H "x-signature: $SIGNATURE" \
-d "$BODY"