Skip to content

Changelog

API changelog

Every change to the Ureh API, newest first. Breaking changes, deprecations and security updates are called out so you can tell at a glance whether a release asks anything of your integration.

Latest release v1.6

v1.6
Breaking Change

Unresolved transactions no longer report as failed #

A purchase whose upstream result is not yet known reports reconciliation_required rather than failed. The previous model collapsed unknown outcomes into failure, which was wrong often enough to cost money.

Affected

  • GET /v1/transactions/{reference}
  • webhook: transaction.updated

Action required

Treat reconciliation_required as an open outcome and keep the purchase pending. Refunding locally on this state will refund purchases that later settle.

The transaction state machine previously had no way to express an outcome it had not yet established. A provider response that was ambiguous, delayed or absent was recorded as failed, which is a terminal state, and a meaningful share of those purchases went on to settle successfully once requeried.

Unresolved outcomes now have their own state. It is not terminal and it is not a failure; a requery or an operator moves it to success or failed. Terminal states remain exactly what they were, so an integration that already waited for one is unaffected.

Migration guidance

Where a status check previously tested for failure by exclusion, test for the terminal states explicitly:

- if (state !== "success") markFailed()
+ if (state === "failed")  markFailed()
+ if (state === "success") markSettled()
+ // pending, queued, processing, reconciliation_required: still open
v1.6
Changed

Purchase endpoints share one transaction pipeline #

Airtime, data, cable TV, electricity and education purchases now run through the same validation, pricing, wallet and state machine path. Behaviour that previously varied between products is now identical across all five.

Affected

  • POST /v1/airtime/purchase
  • POST /v1/data/purchase
  • POST /v1/tv/purchase
  • POST /v1/electricity/purchase
  • POST /v1/edu/purchase

Action required

No request or response change. Integrations that special cased one product to work around a difference in error shape or wallet timing can drop that handling.

Each product was originally implemented with its own request handling. The paths converged over time but never exactly, so error envelopes, idempotent replay and the point at which the wallet was debited could differ in small ways between one product and the next. Those differences were invisible until an integration handled several products and had to account for each.

All product now compose the same engines. One pricing resolution, one wallet lock, one state machine, one error envelope. The practical effect is that behaviour learned against one product holds for the rest.

v1.5
Security

Webhook signatures bind the delivery timestamp #

The signature is now computed over the timestamp and the raw body together. Under the previous scheme it covered the body alone, which left a captured delivery replayable for as long as the endpoint accepted it.

Affected

  • webhook deliveries (all events)

Action required

Recompute the signature over timestamp and raw body, then reject deliveries older than your tolerance window. The previous scheme is accepted until the sunset date.

Sunset

Binding the timestamp into the signed payload means it cannot be altered without invalidating the signature, which is what makes an age check meaningful. Verify the signature first, then reject anything older than your tolerance; five minutes is comfortably wider than any legitimate delay and narrower than a useful replay window.

Both schemes are accepted during the transition. After the sunset date only the current one is.

Migration guidance
- signature = HMAC_SHA256(raw_body, signing_secret)
+ signature = HMAC_SHA256(timestamp + "." + raw_body, signing_secret)

Verify against the raw request bytes rather than a re-serialized copy of the parsed JSON, and compare in constant time.

v1.5
Changed

Product catalogue replaces per product variation endpoints #

Data plans, cable bouquets and education products are served from one catalogue shape under /products. The older /variations endpoints returned provider specific payloads that differed by product.

Affected

  • GET /v1/data/products
  • GET /v1/tv/products
  • GET /v1/edu/products

Action required

Move to the /products endpoints. The /variations predecessors remain reachable only under the legacy /v1 prefix and are not carried forward.

The variation endpoints exposed whichever shape the upstream provider happened to return, so a client integrating data plans and cable bouquets wrote two parsers for what is conceptually one list. Switching provider could also change the payload without the endpoint changing.

The catalogue endpoints return one provider independent shape keyed by product_code, which is the same identifier the corresponding purchase call takes. Provider differences are resolved before the response is built.

v1.4
Deprecated

Query addressed transaction lookup superseded #

A transaction is addressed by its reference in the path. The query addressed form returns an identical payload and continues to work until the sunset date.

Affected

  • GET /v1/transactions/status?reference={reference}

Action required

Move to GET /v1/transactions/{reference}. The response shape is unchanged.

Sunset

Two paths to the same resource complicate caching and client routing without offering a choice worth making. The path addressed form is the one carried forward.

The deprecated form is unchanged until the sunset date, after which it returns 404.

Migration guidance
- GET /v1/transactions/status?reference=URH-TXN-9F2C
+ GET /v1/transactions/URH-TXN-9F2C