Skip to main content
Outbound webhooks let ReconLayer push event notifications to your systems, instead of you polling the API. This is the opposite direction of Inbound Webhooks, which covers providers pushing evidence to ReconLayer. The model has three parts:
  • OutboundWebhookEndpoint — a URL you register, with a list of subscribedEvents and a signing secret.
  • OutboundWebhookEvent — a queued event with an eventType and payload, fanned out to every matching endpoint as one or more deliveries.
  • OutboundWebhookDelivery — one delivery attempt record per (event, endpoint) pair, tracked through pendingdelivered / failed / retry_scheduled.

Registering an endpoint

curl -X POST http://localhost:3001/v1/outbound-webhook-endpoints \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Internal ledger sync",
    "url": "https://api.yourcompany.com/hooks/reconlayer",
    "subscribedEvents": ["payment_intent.created"],
    "status": "active"
  }'
name
string
required
A human-readable label shown in the dashboard.
url
string
required
Must be a valid URL. ReconLayer will POST event payloads here.
subscribedEvents
string[]
default:"[\"payment_intent.created\"]"
At least one entry. Use "*" to subscribe to all event types — an endpoint matches an event if subscribedEvents contains the event’s eventType or contains "*".
status
string
default:"active"
active or disabled. (deleted is a terminal status set by DELETE, not settable on create.)
The response to POST /v1/outbound-webhook-endpoints is the only time signingSecret is returned, in the format rlwhsec_<32-hex-chars>. Store it immediately — subsequent reads (GET, PATCH) only return hasSigningSecret: true.
{
  "id": "owe_01h...",
  "organizationId": "org_01h...",
  "name": "Internal ledger sync",
  "url": "https://api.yourcompany.com/hooks/reconlayer",
  "subscribedEvents": ["payment_intent.created"],
  "status": "active",
  "hasSigningSecret": true,
  "signingSecret": "rlwhsec_4f1b9c3a7e8d4f0aa2c5e6b7d8f9a0b1",
  "createdAt": "2026-06-14T09:00:00.000Z",
  "updatedAt": "2026-06-14T09:00:00.000Z"
}
See Create an Outbound Webhook Endpoint.

Managing endpoints

# List endpoints (excludes soft-deleted)
curl http://localhost:3001/v1/outbound-webhook-endpoints \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"

# Update subscribed events or status
curl -X PATCH http://localhost:3001/v1/outbound-webhook-endpoints/owe_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{ "subscribedEvents": ["payment_intent.created", "*"], "status": "active" }'

# Soft-delete an endpoint
curl -X DELETE http://localhost:3001/v1/outbound-webhook-endpoints/owe_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
PATCH requires at least one field. DELETE performs a soft delete — the endpoint’s status is set to deleted, it stops appearing in GET /v1/outbound-webhook-endpoints, and it stops matching new events, but its historical events/deliveries remain queryable. Both PATCH and DELETE return 404 not_found for an unknown or already-deleted outboundWebhookEndpointId. See List Outbound Webhook Endpoints, Update an Outbound Webhook Endpoint, and Delete an Outbound Webhook Endpoint.

Events ReconLayer emits

subscribedEvents accepts any non-empty string, so you can use this system for your own custom event types via POST /v1/outbound-webhook-events (see below). As of this writing, the platform automatically emits one event type as part of its core flow:
Event typeEmitted whenPayload
payment_intent.createdA PaymentIntent is created (directly via the API or via a client_internal_ledger file import row){ paymentIntentId, caseId, externalReference, sourceAmount, sourceCurrency, destinationAmount, destinationCurrency }
This is also the default value of subscribedEvents when creating a new endpoint, and the sourceKey used for deduplication follows the pattern payment_intent.created:{paymentIntentId} (see Deduplication below). Subscribe to "*" if you want to receive every event type ReconLayer emits now and in the future without updating your endpoint configuration each time new event types are added.

Queueing your own events

POST /v1/outbound-webhook-events lets you (or an internal service) queue an arbitrary event for fan-out to matching endpoints — useful for custom integrations or for replaying a notification.
curl -X POST http://localhost:3001/v1/outbound-webhook-events \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "payment_intent.created",
    "sourceKey": "payment_intent.created:pi_01h...",
    "payload": { "paymentIntentId": "pi_01h...", "externalReference": "INV-2026-0099" }
  }'
eventType
string
required
Any non-empty string. Matched against endpoints’ subscribedEvents.
sourceKey
string
Optional dedup key, unique per organization. See below.
payload
object
default:"{}"
Arbitrary JSON delivered to subscribed endpoints.
Response:
{
  "outcome": "queued",
  "event": {
    "id": "owev_01h...",
    "organizationId": "org_01h...",
    "eventType": "payment_intent.created",
    "sourceKey": "payment_intent.created:pi_01h...",
    "status": "pending",
    "endpointCount": 1,
    "payload": { "paymentIntentId": "pi_01h...", "externalReference": "INV-2026-0099" },
    "deliveries": [
      {
        "id": "owd_01h...",
        "organizationId": "org_01h...",
        "endpointId": "owe_01h...",
        "endpointName": "Internal ledger sync",
        "endpointUrl": "https://api.yourcompany.com/hooks/reconlayer",
        "attemptCount": 0,
        "status": "pending",
        "responseStatusCode": null,
        "lastError": null,
        "nextRetryAt": null,
        "attemptedAt": null,
        "deliveredAt": null,
        "createdAt": "2026-06-14T09:00:01.000Z",
        "updatedAt": "2026-06-14T09:00:01.000Z"
      }
    ],
    "createdAt": "2026-06-14T09:00:01.000Z",
    "updatedAt": "2026-06-14T09:00:01.000Z"
  }
}

Deduplication with sourceKey

If sourceKey is provided and an OutboundWebhookEvent with the same (organizationId, sourceKey) already exists, no new event or deliveries are created — the existing event is returned with "outcome": "duplicate". This makes it safe to call repeatedly from a retried operation.

Fan-out and status when nothing is subscribed

When createOutboundWebhookEvent runs, ReconLayer finds every active endpoint whose subscribedEvents contains the event’s eventType or contains "*", and creates one OutboundWebhookDelivery per matching endpoint, each starting in pending. If no endpoint matches, the event is created with status: "skipped" and zero deliveries — it is recorded for audit purposes but nothing is queued for delivery. See Queue an Outbound Webhook Event and List Outbound Webhook Events.

Listing events

curl "http://localhost:3001/v1/outbound-webhook-events?status=pending&limit=25" \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
limit
number
default:"25"
1-100.
offset
number
default:"0"
eventType
string
Filter by exact eventType.
status
string
One of pending, delivered, failed, skipped.
Each event embeds its deliveries[], so you can see per-endpoint outcomes without a separate call.

Recording delivery attempts

POST /v1/outbound-webhook-deliveries/{outboundWebhookDeliveryId}/attempts records the outcome of an attempt to deliver an event to an endpoint — for example, from a delivery worker that performs the actual HTTP POST (signing the payload with the endpoint’s signingSecret) and reports back the result.
curl -X POST http://localhost:3001/v1/outbound-webhook-deliveries/owd_01h.../attempts \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "delivered",
    "responseStatusCode": 200,
    "responseBody": { "ok": true }
  }'
status
string
required
One of pending, retry_scheduled, delivered, failed.
responseStatusCode
number
100-599, or null.
responseBody
object
Arbitrary JSON, or null.
lastError
string
Error description if the attempt failed, or null.
nextRetryAt
string
ISO 8601 timestamp for the next retry, if status: "retry_scheduled".
Each call to this endpoint:
  • Increments the delivery’s attemptCount, sets attemptedAt to now.
  • Sets deliveredAt to now if status: "delivered", otherwise clears it.
  • If status is "delivered" and lastError was not supplied, clears lastError. If status is "retry_scheduled" and nextRetryAt was not supplied, leaves the existing nextRetryAt unchanged; for any other status, nextRetryAt defaults to null unless explicitly provided.
Returns 404 not_found if the delivery doesn’t exist for the organization.

How the parent event’s status is derived

After recording an attempt, ReconLayer recomputes the parent OutboundWebhookEvent.status from all of its deliveries:
ConditionResulting event status
No deliveries at allskipped
Every delivery is delivereddelivered
At least one delivery is pending or retry_scheduled (and not all delivered)pending
Otherwise (all deliveries are terminal and at least one failed)failed
See Record an Outbound Webhook Delivery Attempt.

Security

Each endpoint has a unique signingSecret (rlwhsec_<32-hex>), generated once at creation and never re-displayed. Use this secret to verify that a delivery genuinely originated from your registered endpoint configuration and has not been tampered with — store it securely alongside your endpoint’s URL.

Next steps

  • Ingestion Flowspayment_intent.created events are queued at the same point a PaymentIntent/ReconciliationCase is created.
  • File Importsclient_internal_ledger rows that create PaymentIntents also trigger payment_intent.created outbound events.