0Bit Documentation

Process rail pay-in and pay-out events

Handle approved rail pay-in, pay-out, and quote-consumed events without exposing rail or settlement internals.

Rail events are for approved flows that surface rail pay-in or pay-out status. They should update your product state and support records, but they should not expose provider, treasury, reserve, or settlement implementation details to users.

Rail events are approval-gated

Document and handle rail events only for partner accounts and products that are approved for those event families. Keep provider names and settlement internals out of public docs and customer-facing UI.

Event families

Event familyMeaningRecommended action
rail.pay_in.processingA pay-in has started processing.Mark local record as processing.
rail.pay_in.settledA pay-in settled successfully.Close the pay-in and reconcile value once.
rail.pay_in.failedA pay-in failed.Close or retry based on product policy.
rail.pay_in.cancelledA pay-in was cancelled.Stop downstream fulfillment.
rail.pay_out.processingA pay-out has started processing.Mark local record as processing.
rail.pay_out.settledA pay-out settled successfully.Close the pay-out and notify the user.
rail.pay_out.failedA pay-out failed.Move to support or retry.
rail.pay_out.cancelledA pay-out was cancelled.Stop downstream completion.
quote.consumedA signed quote was redeemed.Attach quote consumption to the related attempt.

Processing flow

Worker pattern

async function processRailEvent(event: ObitWebhookEvent) {
  const railRecord = await railRecords.findByExternalReference(event.data.id);
  if (!railRecord) {
    await supportQueue.enqueue({ reason: 'unknown_rail_reference', eventId: event.id });
    return;
  }

  const inserted = await eventLog.insertOnce({ eventId: event.id, type: event.type });
  if (!inserted) return;

  if (event.type.endsWith('.settled')) {
    await railRecords.settleOnce({ railRecordId: railRecord.id, eventId: event.id });
  } else if (event.type.endsWith('.failed') || event.type.endsWith('.cancelled')) {
    await railRecords.closeOnce({ railRecordId: railRecord.id, eventId: event.id, status: 'closed' });
  } else {
    await railRecords.markProcessing({ railRecordId: railRecord.id, eventId: event.id });
  }
}

Public-safe UI copy

Internal conditionUser-facing copy
Processing"Your transfer is processing."
Settled"Your transfer is complete."
Failed"This transfer could not be completed. Please try again or contact support."
Cancelled"This transfer was cancelled."
Unknown or delayed"We are checking the latest status."

On this page