Orders & Postbacks  ›  Postbacks

Postbacks

A postback is a signed POST request PriceFirst sends to your postbackUrl the moment a customer completes a trade-in. You acknowledge receipt by returning HTTP 200 with your internal transaction ID in the body. Retries, signing and idempotency are documented on separate pages.

End-to-end flow

  1. Customer completes checkout on pricefirst.com.
  2. PriceFirst creates the order with status pending and generates a short order_code (e.g. 58BDGJ97) for customer-facing references.
  3. In the background, PriceFirst POSTs the payload to your postBackUrl with HMAC-signed headers.
  4. You respond HTTP 200 with a JSON body { status: 200, order_id } within 30 seconds.
  5. PriceFirst stores the transaction ID, marks the order delivered, and emails a trade-in confirmation to the customer.
  6. If delivery fails, PriceFirst logs the attempt, marks the order failed, and (unless retries are disabled) queues a retry with exponential backoff. See Retries & Idempotency.

Endpoint registration

Each marchat/business registers a single HTTPS URL on their marchant profile:

FieldDescription
postBackUrlYour postback endpoint. HTTPS only.
postbackTokenShared secret sent as X-PriceFirst-Token. Auto-generated on marchat save.
postbackHmacSecretHMAC-SHA256 secret, used to sign the body. Auto-generated on marchat save.
postbackRetryEnabledWhether failed postbacks are automatically retried. Default true.
postbackMaxAttemptsMaximum total delivery attempts (including the first). Default 6.

postbackToken and postbackHmacSecret are given once during onboarding. Store them and use them to verify every incoming postback — see Signing & Security.

Request

  • Method: POST
  • URL: Marchants postBackUrl
  • Content-Type: application/json
  • Timeout: 30 s
  • Headers sent (see Signing & Security for the full spec):
HeaderExample
User-AgentPriceFirst-OrderSystem/1.0
X-PriceFirst-TokenpWvT1… shared secret
X-PriceFirst-Timestamp1714752100 (Unix seconds)
X-PriceFirst-Signaturea0f3… HMAC-SHA256 hex
X-PriceFirst-AlgorithmHMAC-SHA256
X-PriceFirst-Idempotency58BDGJ97 (same as order_code)

Payload

BACS / bank transfer example:

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "phone": "+447700900000",
  "postcode": "ME14BJ",
  "address": "7 Ashford Road",
  "additionalAddress": "Sterling House",
  "city": "Maidstone",
  "country": "United Kingdom",
  "recycler_name": "We Buy Any Phone",
  "device_category": "Mobile Phones",
  "device_id": "PF-67e144bc64b5fa0aba8e5236",
  "device_name": "iphone 16e",
  "device_price": 350,
  "device_assessment": {
    "storage": "128gb",
    "condition": "excellent",
    "network": "unlocked"
  },
  "payment_method": "bacs",
  "bank_account_name": "Jane Doe",
  "bank_sort_code": "123456",
  "bank_account_number": "12345678",
  "postage_option": "free_post_bag",
  "order_code": "58BDGJ97"
}

PayPal example (only the payment fields change):

{
  "payment_method": "paypal",
  "paypal_email": "jane.paypal@example.com"
}

Field reference

FieldTypeNotes
namestringCustomer name, snapshot at order time.
emailstringCanonical customer email.
phonestringE.164 recommended.
postcodestringUK postcode of the sender address.
addressstringStreet address line 1.
additionalAddressstring/optionalAddress line 2.
citystringCity / town.
countrystringDefaults to United Kingdom.
recycler_namestringYour company or marchant name.
device_categorystringSystem category (Mobile Phones, iPad, …).
device_idstringThe Device ID you supplied in your feed.
device_namestringLowercased model string from the feed.
device_pricenumberPrice locked at order time (GBP, already 50p-rounded).
device_assessmentobject{ storage, condition, network } — all normalised slugs.
payment_methodbacs | paypalDetermines which payment fields are present.
bank_*stringsPresent when payment_method = bacs.
paypal_emailstringPresent when payment_method = paypal.
postage_optionenumfree_post_bag or free_post_label.
order_codestringShort, customer-facing unique ID (8 chars). Use in UI / support.

Always dedupe on order_code (which matches the X-PriceFirst-Idempotency header). It's guaranteed unique and safe to use as your external order reference.

Response

Your endpoint must respond with:

  • HTTP status 200 OK (any other status is treated as a failure).
  • Content-Type: application/json.
  • A JSON body with:
    • status — the number 200 (must match the HTTP status).
    • order_id — your internal transaction / order identifier as a non-empty string (up to 256 chars).
HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": 200,
  "order_id": "TX-1029384756"
}

Additional keys are allowed and ignored.

Plain-text bodies, empty bodies, and { ok: true }-style acknowledgements are all treated as failures. The body must be a JSON object containing both status: 200 and a non-empty order_id string. See the Test Harness error codes for a full list of rejection reasons.

Error responses

If you can't process the postback, return a non-200 HTTP status with a JSON body describing the problem:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "status": 401,
  "error": "bad_signature",
  "message": "HMAC signature did not match"
}

PriceFirst does not parse error bodies beyond logging them — any non-200 HTTP status simply triggers the retry pipeline. Use meaningful codes (400 bad payload, 401 bad token/signature, 409 duplicate, 422 validation, 5xx your problem).

Postage options

postage_option valueDescription
free_post_bagCustomer has chosen a physical postage bag.
free_post_labelCustomer has chosen a postage label via email.

Further postage options (courier collection + collection_date) are not currently supported. They are reserved for a future release and this doc will be updated accordingly.

Conditions

device_assessment.condition is the normalised base condition — one of new, excellent, good, poor, broken. See Recycling Feeds for the alias map. If you need the marchant-supplied label from your feed, look it up via device_id.

Next