Live Decisioning
The synchronous approve/decline webhook Penny calls during authorization.
Live Decisioning is a synchronous, blocking request Penny sends to one of your webhook endpoints in the middle of authorizing a transaction — not the asynchronous, fire-and-forget events described in Webhooks Overview. Penny waits for your response (or a timeout) before the transaction can proceed, so this is fundamentally different from ordinary event delivery.
Live Decisioning is configured through a Spend Control’s live_decisioning rule, not through a webhook’s trigger_events subscriptions. See Spend Controls for where this fits among the other rule types and the hierarchy it’s evaluated in.
How it’s set up
A live_decisioning rule has two required configuration fields:
The rule configuration has no timeout field. By default, Penny waits 1 second for your response. The timeout in effect for each request is sent as request.timeout_seconds.
The request Penny sends
Penny sends a JSON POST to the configured endpoint. Optional values are shown as null where unavailable; actual values vary with the authorization.
exchange_id is the decision exchange identifier, and type and request.identifier are transaction.authorization. request.subject identifies the transaction. request.fallback is approved or declined, derived from the rule’s boolean fallback_result; request.timeout_seconds is the effective timeout, sent as a JSON number.
authorization.type is the authorization direction (debit, credit, or adjustment), not a payment channel. Optional IDs, amounts, and detail fields may be null.
Every amount uses { "amount": "<decimal string>", "currency": "<ISO 4217 code>" }. For a single-currency transaction, purchase_amount may be null; use base_amount for the amount Penny authorizes.
The response Penny expects
Respond within the timeout with 2xx and this body:
outcome—"approved"or"declined". Required.reason— optional free-text explanation, useful for your own audit trail; Penny doesn’t act on its content beyond storing it.
Any of the following is treated as a failure, and the rule’s fallback_result is applied instead:
- No response within
timeout_seconds. - A connection error or non-2xx status code.
- A 2xx response whose body is missing, isn’t a JSON object, or has an
outcomevalue other thanapproved/declined.
Verifying the request
Live Decisioning requests go through the exact same signing mechanism as ordinary event deliveries — if the endpoint has a signing secret configured, the request carries Penny-Webhook-Timestamp and Penny-Webhook-Signature headers computed the same way, and if it has an authentication_token, the request carries the same Authorization: Bearer header. See Verifying webhook signatures and Webhook Authorization for both checks.
Evaluation order
Only the smallest control surface with a live_decisioning rule attached is called — a matching rule at a larger control surface is skipped entirely once a smaller one has run. See Spend Controls → Multiple applicable controls for the full hierarchy and a worked example.
The decision exchange record
Every Live Decisioning request Penny sends is recorded as a decision exchange: one request/response round trip with your endpoint. Penny keeps the same record whether the exchange resolves normally, times out, or fails, and you can read it through the API:
GET /notifications/webhooks/exchangeslists exchanges for one endpoint.notification_endpoint_idis required, and results are paginated like any other list endpoint.GET /notifications/webhooks/exchanges/{notification_decision_exchange_id}retrieves a single exchange. Pass?version=to look up a specific past version; it defaults to0, which always resolves to the latest.
The exchange response looks like this (request.authorization abridged):
status reflects exactly how the exchange resolved:
Both timed_out and failed apply the rule’s fallback_result to outcome in exactly the same way — the only difference between them is whether anything came back from your endpoint at all.
The supported Live Decisioning type is transaction.authorization; the request shape above applies to that type.