Class: Webhooks::V1::CurriController

Inherits:
BaseController
  • Object
show all
Defined in:
app/controllers/webhooks/v1/curri_controller.rb

Overview

Controller for Curri delivery-status webhooks.

Curri POSTs the full delivery JSON (status, driver, GPS, images,
cancellationReason) every ~20 seconds until the delivery is terminal
(delivered/canceled). See docs.curri.com → Webhooks.

The destination is set by CURRI'S TEAM, not by us. app.curri.com →
Account → Profile → Integration info DISPLAYS "Webhooks endpoint URL
(Live)" and "…(Sandbox)" beside the User ID and API keys, but the
fields are read-only — we can see them and not edit them (established
2026-08-05 by trying; an earlier revision of this comment claimed they
were self-service, inferred from a screenshot showing the labels).
Changing the URL therefore means emailing Curri, which makes rotating
curri.webhook_token a two-party operation: edit credentials, send the
new URL, and expect a window where inbound webhooks 401 until they
apply it. Both fields carry the SAME token (see #verify_token!) — with
a two-party round-trip per rotation, a second secret would be a second
thing to keep in sync and a second way for sandbox to sit 401-ing
unnoticed. Live and sandbox are separated by HOSTNAME
(api.warmlyyours.com vs api.warmlyyours.ws), not by credential.

Authentication: Curri does NOT sign webhooks and sends no
distinguishing headers, so the ONLY auth is a shared token embedded in
the URL we hand them (?token=…, from curri.webhook_token in
encrypted credentials) — same self-auth approach as the ShipEngine
webhook's Basic-auth-in-URL. Fails closed outside dev/test when the
token is unconfigured.

Idempotency: the ~20s refire cadence means the same status arrives
dozens of times (with fresher GPS each time). external_id
"<delivery_id>_" lets WebhookLog.ingest! collapse refires of
the same status; only status TRANSITIONS produce new work.

See Also:

  • doc/tasks/202607241449_CURRI_API_INTEGRATIONdoc/tasks/202607241449_CURRI_API_INTEGRATION.md

Instance Method Summary collapse

Instance Method Details

#createvoid

This method returns an undefined value.

POST /webhooks/v1/curri?token=…

Ingests one Curri delivery-status payload into WebhookLog and
enqueues async processing.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'app/controllers/webhooks/v1/curri_controller.rb', line 51

def create
  payload = json_payload
  delivery_id = payload['id']
  status = curri_status(payload)

  if delivery_id.blank? || status.blank?
    Rails.logger.warn "[Curri Webhook] Payload missing delivery id or status: #{raw_payload.to_s.first(300)}"
    return head :bad_request
  end

  resource = Delivery.find_by(freight_order_number: delivery_id)
  Rails.logger.info "[Curri Webhook] #{delivery_id} -> #{status} (delivery: #{resource&.id || 'unresolved'})"

  webhook_log = WebhookLog.ingest!(
    provider: 'curri',
    category: 'delivery_update',
    resource_type: resource&.class&.name || 'Delivery',
    resource_id: resource&.id,
    external_id: "#{delivery_id}_#{status}",
    data: payload,
    notes: "Curri delivery update: #{delivery_id} -> #{status}"
  )

  WebhookProcessorWorker.perform_async(webhook_log.id)

  head :ok
end