> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction History

> Persisted transaction records, on-chain monitoring, and webhooks for REST API broadcasts

REST broadcasts create persisted transaction records that Para tracks to terminal status on-chain. Records are queryable, auditable, and emit webhooks when they confirm or fail.

Rows are written for `POST /v1/wallets/{walletId}/transfer` broadcasts and for `POST /v1/wallets/{walletId}/sign-transaction` when `broadcast: true`. Sign-only calls do not write rows.

## Status Lifecycle

```
pending → submitted → confirmed
                    → reverted
                    ↘ failed
```

| Status      | Meaning                                                                                           |
| ----------- | ------------------------------------------------------------------------------------------------- |
| `pending`   | Record inserted; broadcast not yet attempted.                                                     |
| `submitted` | RPC accepted the signed bytes. Monitor is running. Not terminal.                                  |
| `confirmed` | Included in a block, not reverted. `blockNumber` and `blockHash` are populated.                   |
| `reverted`  | Included on-chain but execution failed (EVM status=0 or Solana signature error). Terminal.        |
| `failed`    | Never broadcast, or broadcast was rejected by the RPC. `failureStage` identifies where. Terminal. |

<Note>
  Partner code must treat unknown status values as non-terminal. Para may add new statuses (`finalized`, `replaced`) in a later release without bumping the API version.
</Note>

## Response Shape

Calls that broadcast return a `transactionId` field and set an `x-transaction-id` response header. Use either to look up the record afterward.

```json theme={null}
{
  "signedTransaction": "0x02f8...",
  "txHash": "0x1234abcd...",
  "transactionId": "550e8400-e29b-41d4-a716-446655440000"
}
```

<Note>
  For `sign-transaction`, omitted `broadcast` is also sign-only. No record is written and no `transactionId` is returned.
</Note>

If a broadcast request fails after Para creates the transaction row, the error response still sets `x-transaction-id` and includes `transactionId` in the JSON body. Failures after signing also include `signedTransaction`. Use the transaction history record's `failureStage` and `failureCode` fields for persisted failure details.

## Reading Transaction History

### List transactions for a wallet

```bash theme={null}
curl -H "X-API-Key: $PARA_API_KEY" \
  "https://api.beta.getpara.com/v1/wallets/$WALLET_ID/transactions?status=confirmed&limit=20"
```

Filter by status via `?status=`. Filter by source operation via `?intentKind=transfer` or `?intentKind=sign_transaction`. Paginate via `?cursor=` using the opaque cursor returned in each response. Results are ordered by `createdAt` DESC.

### Look up a single transaction

```bash theme={null}
curl -H "X-API-Key: $PARA_API_KEY" \
  "https://api.beta.getpara.com/v1/wallets/$WALLET_ID/transactions/$TX_ID"
```

Records that belong to a different partner return `404` with an identical body to missing records.

## Record Fields

Each record includes `intentKind`, either `transfer` or `sign_transaction`.

For EVM `sign_transaction` records, Para stores `chainId`, `to`, and `value` when `value` was present in the request. `tokenAddress` is absent. Para does not decode calldata.

For Solana `sign_transaction` records, `to`, `value`, and `tokenAddress` are absent because Para does not decode arbitrary Solana instructions.

## Polling Recipe

When webhooks aren't practical, poll the record until it leaves the `submitted` state.

```typescript theme={null}
async function waitForConfirmation(walletId: string, transactionId: string) {
  const start = Date.now();
  let delayMs = 2000;
  const timeoutMs = 10 * 60 * 1000;

  while (Date.now() - start < timeoutMs) {
    const res = await fetch(
      `https://api.beta.getpara.com/v1/wallets/${walletId}/transactions/${transactionId}`,
      { headers: { 'X-API-Key': process.env.PARA_API_KEY! } },
    );
    const record = await res.json();

    if (record.status === 'confirmed' || record.status === 'reverted' || record.status === 'failed') {
      return record;
    }
    await new Promise((r) => setTimeout(r, delayMs));
    delayMs = Math.min(delayMs * 1.5, 15000);
  }
  throw new Error('timed out waiting for confirmation');
}
```

## Webhooks

Subscribe to two event types from the [Developer Portal](https://developer.getpara.com) to receive terminal-state notifications automatically:

| Event                        | When it fires                                                              |
| ---------------------------- | -------------------------------------------------------------------------- |
| `rest.transaction.confirmed` | Transaction included in a block without reverting.                         |
| `rest.transaction.failed`    | Transaction reverted on-chain, or monitor detected a terminal RPC failure. |

Webhook bodies use the standard envelope described in the [webhooks guide](/v3/general/webhooks). The `data.transactionId` field matches the `transactionId` returned by `transfer` or `sign-transaction` with `broadcast: true`.

Payload `data` example for `rest.transaction.confirmed`:

```json theme={null}
{
  "transactionId": "550e8400-e29b-41d4-a716-446655440000",
  "walletId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "partnerId": "11111111-2222-3333-4444-555555555555",
  "intentKind": "transfer",
  "type": "EVM",
  "hash": "0x1234abcd...",
  "blockNumber": "5127103",
  "blockHash": "0xdeadbeef...",
  "resolvedAt": "2026-04-23T12:00:00.000Z"
}
```

`rest.transaction.failed` mirrors the shape and adds `status` (`reverted` or `failed`), plus optional `failureStage`, `failureCode`, and `failureMessage` fields.

### Backend receiver pattern

Mobile and web clients do not receive REST transaction webhooks directly. Your backend receives the webhook, verifies it with the secret from the Developer Portal, updates its transaction row, and exposes the latest status to the client.

In this example, `verifyParaWebhook` is the HMAC check described in the [webhooks guide](/v3/general/webhooks#verify-webhook-signatures).

```typescript webhook-route.ts theme={null}
app.post("/webhooks/para", express.raw({ type: "application/json" }), async (req, res) => {
  if (!verifyParaWebhook(req.body, req.headers, process.env.PARA_WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString("utf8"));

  if (event.type === "rest.transaction.confirmed") {
    await db.transactions.update(event.data.transactionId, {
      status: "confirmed",
      txHash: event.data.hash,
      blockNumber: event.data.blockNumber,
      resolvedAt: event.data.resolvedAt,
    });
  }

  if (event.type === "rest.transaction.failed") {
    await db.transactions.update(event.data.transactionId, {
      status: event.data.status,
      txHash: event.data.hash,
      failureStage: event.data.failureStage,
      failureCode: event.data.failureCode,
      failureMessage: event.data.failureMessage,
      resolvedAt: event.data.resolvedAt,
    });
  }

  res.sendStatus(204);
});
```

Store the `transactionId` when you submit the REST broadcast. In the webhook, use `event.data.transactionId` to update that same row.

<Note>
  Sign-only calls and transactions you broadcast yourself do not emit REST transaction webhooks. Para only sends these webhooks for broadcasts it can monitor.
</Note>

## Failure Fields

When `status = failed`, `failureStage` describes where the pipeline broke:

| Stage             | Meaning                                                                                                                                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mpc_sign`        | The MPC signing ceremony errored before a signature was produced.                                                                                                                                    |
| `signature_apply` | The signature could not be attached to the transaction.                                                                                                                                              |
| `signer_verify`   | The recovered signer address did not match the wallet's public address (EVM only).                                                                                                                   |
| `broadcast`       | The RPC node rejected the signed bytes (e.g. `INSUFFICIENT_NATIVE_BALANCE`, `EXECUTION_FAILED`).                                                                                                     |
| `monitor_timeout` | The signed transaction was broadcast but didn't reach a terminal on-chain state within Para's 30-minute monitor window. The transaction may still confirm later. Query the chain directly to verify. |

When `status = reverted`, the transaction reached the chain and executed with a revert. `blockNumber` and `blockHash` are populated, `failureStage` is absent, and `failureMessage` contains any error detail surfaced by the monitor.

## Broadcast Error Messages

REST `/transfer` now sources broadcast-failure messages from Para's shared broadcast helper rather than passing through raw ethers.js strings. If code parses the previous raw string format, update it to read `failureCode` (e.g. `INSUFFICIENT_NATIVE_BALANCE`, `EXECUTION_FAILED`) from the transaction record or failure webhook instead.

## Retention

Both `/transfer` broadcasts and `sign-transaction` calls with `broadcast: true` write rows. Retention remains deferred until the `rest_transfers` table reaches an agreed large-row or disk threshold.
