Developers
Record lifecycle
A record is one wait billed as a charge. These are the statuses it moves through, what moves it from one to the next, and the webhook each move sends.
On this page
Statuses
A record's status is one of these. Carriers see every status; a fuel buyer's key sees a record only once it was sent to them. New statuses may be added, so treat one you don't know as still in progress.
| Status | What it means | Final | Buyer sees it |
|---|---|---|---|
| recordingRecording | The driver's clock is running: the wait is in progress (carriers only). | No | No |
| draftDraft | The clock stopped; the driver hasn't submitted the record yet (carriers only). | No | No |
| pending_reviewCarrier Review | Waiting for the carrier's review before it goes to the fuel buyer (carriers only). | No | No |
| rejected_by_employerRejected by Carrier | The carrier's review rejected its driver's record. It's never billed; the carrier can reopen it (carriers only). | No | No |
| sent_to_customerAwaiting Customer | With the fuel buyer, to approve or dispute. | No | Yes |
| disputedDisputed | The buyer disputed it. The carrier corrects and resends it, accepts the buyer's offer, escalates, or closes it. | No | Yes |
| escalatedEscalated | The carrier escalated the dispute. The buyer approves or closes it. | No | Yes |
| customer_approvedApproved | The buyer approved it: it's owed (amount_approved). Unpaid until a payment completes. | No | Yes |
| payment_pendingPayment Pending | A payment is on its way: a bank payment is processing, or the buyer marked it paid outside Demurly and the carrier hasn't confirmed it. | No | Yes |
| paid_stripePaid | Paid through Demurly (card or bank). Final, unless the payment is refunded or lost to a chargeback, which puts it back to customer_approved. | Yes | Yes |
| paid_externalPaid (Outside Demurly) | Paid outside Demurly, confirmed by the carrier or its accounting system. Final. | Yes | Yes |
| cancelledCancelled | The carrier cancelled it before the buyer answered. Final; never billed. | Yes | No |
| closedClosed | A dispute ended without payment. Final. | Yes | Yes |
Diagram
cancelled. Imported past records go straight from draft to closed or paid_external.What moves a record
Each move is an event in the record's history (its type is the first column) and sends the webhook in the last column to every endpoint subscribed to it. Moves marked with an endpoint can be made through the API; the rest happen in the web app, the driver app, or by themselves.
(new record)torecording
A driver starts the clock in the driver app; a reported wait (API, EDI 214, import) is created and passes straight through to pending_review
Event
record_createdWebhookrecord.createdAPIPOST /api/v1/recordsrecordingtodraft
The driver stops the clock, or it stops itself (the truck drove off); a reported wait stops at its end_time
Event
gps_tracking_stoppedWebhookrecord.stoppedAPIPOST /api/v1/recordsdrafttopending_review
The driver submits it for review; a reported wait is submitted at once
Event
record_submittedWebhookrecord.submittedAPIPOST /api/v1/recordspending_reviewtorejected_by_employer
A carrier admin rejects the driver's record in review (web app)
Event
employer_rejectedWebhookrecord.rejected_internalrejected_by_employertopending_review
A carrier admin takes a review rejection back (web app)
Event
record_reopenedWebhookrecord.reopenedpending_review | disputedtosent_to_customer
The carrier sends it to the fuel buyer, or resends it after correcting a disputed charge (send: true on create does it in the same call)
Event
record_sent_to_customerWebhookrecord.sentAPIPOST /api/v1/records/{id}/senddisputedtosent_to_customer
The carrier accepts the buyer's counter-offer from a dispute: back to the buyer at that amount (web app)
Event
dispute_offer_acceptedWebhookrecord.offer_acceptedsent_to_customer | disputed | escalatedtocustomer_approved
The fuel buyer approves it (API, web app, or an emailed review link)
Event
record_approvedWebhookrecord.approvedAPIPOST /api/v1/records/{id}/approvesent_to_customer | disputed | escalatedtocustomer_approved
The fuel buyer approves a lower amount (web app)
Event
record_partially_approvedWebhookrecord.approvedsent_to_customertodisputed
The fuel buyer rejects it (the older form of a dispute)
Event
record_rejectedWebhookrecord.disputedsent_to_customertodisputed
The fuel buyer disputes it with a reason (API, web app, or an emailed review link)
Event
record_disputedWebhookrecord.disputedAPIPOST /api/v1/records/{id}/disputedisputedtoescalated
The carrier escalates the dispute (web app)
Event
record_escalatedWebhookrecord.escalateddisputedtoescalated
The carrier escalates the dispute (web app)
Event
dispute_escalatedWebhook nonedisputed | escalatedtoclosed
The carrier accepts the buyer's dispute, or the buyer rejects an escalated charge (web app): closed unpaid
Event
record_closedWebhookrecord.closeddrafttoclosed
A past record from the carrier's spreadsheet import lands closed, or paid outside Demurly when its row says so
Event
record_importedWebhook nonedrafttopaid_external
A past record from the carrier's spreadsheet import lands closed, or paid outside Demurly when its row says so
Event
record_importedWebhook nonerecording | draft | pending_review | sent_to_customertocancelled
A carrier admin cancels it, before the buyer has answered (web app)
Event
record_cancelledWebhookrecord.cancelledcustomer_approvedtopayment_pending
The buyer starts a bank payment in Demurly, or hands it to its accounts payable
Event
payment_processingWebhookrecord.payment_pendingpayment_pendingtocustomer_approved
A payment fails: the record is approved and unpaid again
Event
payment_failedWebhookrecord.payment_failedcustomer_approved | payment_pendingtopaid_stripe
A card or bank payment through Demurly completes
Event
payment_completedWebhookrecord.paidcustomer_approvedtopayment_pending
The buyer marks it paid outside Demurly (web app, or its accounting system's bill was paid)
Event
external_payment_markedWebhookrecord.payment_pendingcustomer_approved | payment_pendingtopaid_external
The carrier confirms the outside payment arrived
Event
external_payment_confirmedWebhookrecord.paidcustomer_approvedtopaid_external
The carrier records an outside payment, or its accounting system shows the invoice paid
Event
external_payment_recordedWebhookrecord.paidpayment_pendingtocustomer_approved
The carrier says the outside payment never arrived
Event
external_payment_rejectedWebhookrecord.payment_failedpaid_stripetocustomer_approved
A card payment is fully refunded or lost to a chargeback
Event
payment_reversedWebhookrecord.payment_reversed
Some webhooks don't follow a status change: record.withdrawn (a charge you were sent was sent to another buyer or taken back for review by the carrier; data.record is the tombstone the API shows you (buyers only)), record.updated (a record's details or billed times changed (a field edit, or times adjusted, which changes the amount); data.event.type says which) and record.comment_added (someone on the carrier or the buyer side commented on a record).
A wait reported through POST /api/v1/records runs its first moves in one call: it's created, stopped at its end_time, calculated and submitted, so a carrier's endpoint gets record.created, record.stopped and record.submitted (and record.sent with send: true).
Owed or paid?
For an accounts payable or receivable system, group the statuses like this:
- Owed (approved, not paid yet):
customer_approved, payment_pending. Useamount_approved. - Waiting for the buyer:
sent_to_customer; in dispute:disputed,escalated. - Paid:
paid_stripe, paid_external(paid_at). A card payment can still be reversed, which brings the record back tocustomer_approvedwith arecord.payment_reversedwebhook. - Never billed:
cancelled,closed,rejected_by_employer, and a buyer's tombstones (below).
Withdrawn charges
A charge a fuel buyer was sent never just disappears from the buyer's view. If the carrier cancels it, sends it to another buyer or takes it back for review, the buyer's key gets a tombstone in its place: status: "withdrawn" with withdrawn_reason and withdrawn_at (the record tombstone object). withdrawn isn't one of the record's statuses: it's what that buyer sees. The buyer gets record.withdrawn (or record.cancelled for a cancellation). If the charge is sent back to them, it's an ordinary record again.
History event types
GET /api/v1/records/{id} with include=events lists a record's history, oldest first; a webhook names the event behind it in data.event.type. These are the types. New ones may be added.
| Type | What happened | Webhook |
|---|---|---|
| record_created | The record was made (a driver started the clock, or a wait was reported) | record.created |
| gps_point_captured | A GPS point from the driver's phone (not listed by the API; summarized in gps_evidence) | none |
| gps_tracking_stopped | The clock stopped | record.stopped |
| photo_captured | The driver took a photo during the wait | none |
| document_attached | A photo or document was attached (for example through the photos endpoint) | none |
| attachment_replaced | An attachment was replaced before the record was sent | none |
| field_updated | A detail changed (load or BOL number, terminal, customer...): data.field and data.value | record.updated |
| field_corrected | The carrier corrected a detail in review or in answer to a dispute | record.updated |
| record_submitted | Submitted for the carrier's review | record.submitted |
| record_reviewed | The carrier marked it reviewed (no status change) | none |
| record_sent_to_customer | Sent to the fuel buyer (again, after a dispute, with resubmission and the carrier's message) | record.sent |
| record_approved | The buyer approved it (data.approvedAmount; isGuestApproval and guestEmail when approved from an emailed link) | record.approved |
| record_partially_approved | The buyer approved a lower amount | record.approved |
| record_rejected | The buyer rejected it (older form of a dispute) | record.disputed |
| record_escalated | The carrier escalated a dispute | record.escalated |
| record_closed | A dispute ended without payment | record.closed |
| record_cancelled | The carrier cancelled it | record.cancelled |
| payment_initiated | A payment was started (no status change) | none |
| payment_completed | A payment through Demurly completed | record.paid |
| payment_failed | A payment failed (data.method, failureCode, failureMessage in webhooks) | record.payment_failed |
| external_payment_marked | Marked paid outside Demurly (data.method, reference, paid_on) | record.payment_pending |
| external_payment_confirmed | The carrier confirmed an outside payment | record.paid |
| comment_added | Someone commented (data.comment) | record.comment_added |
| calculation_performed | The charge was calculated (data.amount, total_minutes, billable_minutes, hourly_rate) | none |
| rate_alert_triggered | The rate is outside what the buyer expects (a warning, no status change) | none |
| geofence_alert_triggered | The driver's position left the terminal's geofence (a warning) | none |
| conflict_resolved | An edit conflict was resolved | none |
| time_drift_alert | The phone's clock was off from Demurly's (a warning) | none |
| record_disputed | The buyer disputed it (data.reason, disputeType, proposedAmount) | record.disputed |
| employer_rejected | The carrier's review rejected its driver's record | record.rejected_internal |
| payment_processing | A bank payment is processing, or it was handed to accounts payable | record.payment_pending |
| record_imported | Imported from the carrier's spreadsheet of past records | none |
| external_payment_rejected | The carrier said an outside payment never arrived (data.note) | record.payment_failed |
| payment_reversed | A card payment was refunded or lost to a chargeback | record.payment_reversed |
| time_adjusted | The carrier adjusted the billed times in answer to a dispute (adjusted_start_time, adjusted_end_time, reason, previous_amount) | record.updated |
| record_reopened | The carrier took a review rejection back | record.reopened |
| photo_removed | The driver removed a photo while recording | none |
| dispute_offer_accepted | The carrier accepted the buyer's counter-offer | record.offer_accepted |