Skip to main content

Overview

Webhooks let your application react to events in real time without polling. When something happens in Arcus — an order is confirmed, a payment is received, a shipment is created, an inventory adjustment is posted — Arcus sends an HTTP POST to your endpoint with a signed JSON payload. All webhook requests are signed with HMAC-SHA256. You verify the signature using the signing secret returned at endpoint creation.

Event structure

Every webhook payload uses this envelope:
{
  "id": "evt_01H...",
  "object": "event",
  "type": "order.confirmed",
  "created_at": "2026-05-01T10:30:00Z",
  "api_version": "2026-05-01",
  "entity_id": "ent_01H...",
  "data": {
    "object": {
      "id": "ord_01H...",
      "object": "order",
      "document_type": "sales_order",
      "status": "confirmed"
    }
  },
  "livemode": true
}
FieldTypeDescription
idstringUnique event ID. Use this for deduplication.
typestringEvent name (e.g. order.confirmed). See event catalog below.
created_atISO 8601When the event fired.
api_versionstringThe API version used to serialize data.object.
entity_idstringThe entity this event belongs to.
data.objectobjectThe full resource at the time of the event.
livemodebooleantrue for production events; false for test-mode events.

Registering an endpoint

curl -X POST https://api.arcuserp.com/v1/entities/$ARCUS_ENTITY_ID/webhook_endpoints \
  -H "Authorization: Bearer $ARCUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.example.com/arcus-webhook",
    "enabled_events": ["order.confirmed", "payment.succeeded", "fulfillment.shipped"],
    "description": "Production webhook for order pipeline"
  }'
The response includes a secret in whsec_<hex> format. Store it securely — it is shown once and cannot be retrieved again. Use it to verify every incoming request.
{
  "id": "whe_01H...",
  "object": "webhook_endpoint",
  "url": "https://your-server.example.com/arcus-webhook",
  "status": "active",
  "enabled_events": ["order.confirmed", "payment.succeeded", "fulfillment.shipped"],
  "secret": "whsec_...",
  "created_at": "2026-05-01T10:30:00Z"
}
Field name: use enabled_events (Stripe convention) in all requests and responses. The legacy alias events is accepted for backward compatibility but is deprecated and will be removed in API v2.

Wildcard subscriptions

Subscribe to an entire event family with order.*, or all events with *:
{ "enabled_events": ["order.*", "payment.*"] }
Valid wildcard patterns are <family>.* (14 families) or * (all 118 events).

Duplicate endpoint guard

Registering the same URL + mode combination twice returns HTTP 409 with error: "duplicate_url" and the existing_id of the already-active endpoint.

Verifying signatures

Every webhook request includes an Arcus-Signature header. Always verify it before processing the payload. The signature is an HMAC-SHA256 of <timestamp>.<raw_body> using your signing secret, with a 5-minute replay window. Header format: Arcus-Signature: t=<unix_epoch>,v1=<hex_hmac_sha256>

Webhook request headers

Every delivery includes these headers:
HeaderDescription
Arcus-Signaturet=<unix_epoch>,v1=<hex_hmac_sha256>
Arcus-Webhook-IdThe webhook endpoint ID
Arcus-Event-IdThe event ID (use for deduplication)
Arcus-Event-TypeThe event type string (e.g. order.confirmed)
Arcus-API-VersionThe API version used to serialize the payload
Content-Typeapplication/json

Responding to webhooks

Return a 2xx response within 5 seconds. If your endpoint takes longer, respond immediately and process the event asynchronously. Arcus retries on any non-2xx response or connection failure, with exponential backoff:
AttemptDelay after previous
1Immediate
21 minute
35 minutes
430 minutes
52 hours
612 hours
724 hours
After 6 failed attempts, no more retries are made for that delivery. An endpoint that accumulates 10 consecutive permanent failures is automatically disabled.

Deduplication

Events may be delivered more than once (network timeouts, retries). Always deduplicate on event.id before processing:
const eventId = req.headers['arcus-event-id'];
const existing = await db.processedEvents.findUnique({ where: { id: eventId } });
if (existing) return res.status(200).send('already processed');

await db.processedEvents.create({ data: { id: eventId } });
await processEvent(event);

Rotating signing secrets

curl -X POST https://api.arcuserp.com/v1/entities/$ARCUS_ENTITY_ID/webhook_endpoints/$WEBHOOK_ID/rotate_secret \
  -H "Authorization: Bearer $ARCUS_API_KEY"
The response returns a new signing_secret. Update your environment variable immediately. The old secret is invalidated at rotation — there is no grace period.

Sending a test event

Send a webhook.test event to verify your endpoint is reachable:
curl -X POST https://api.arcuserp.com/v1/entities/$ARCUS_ENTITY_ID/webhook_endpoints/$WEBHOOK_ID/test \
  -H "Authorization: Bearer $ARCUS_API_KEY"

Event catalog

Arcus emits 118 event types across 14 families. Subscribe to individual events, a whole family (order.*), or everything (*).

account (12 events)

EventDescription
account.createdA new account is created
account.updatedAccount fields are updated
account.deletedAn account is soft-deleted
account.mergedTwo accounts are merged
account.address.addedAn address is added to an account
account.address.updatedAn account address is updated
account.address.removedAn account address is removed
account.contact.addedA contact is added to an account
account.contact.updatedAn account contact is updated
account.contact.removedAn account contact is removed
account.credit_limit.updatedThe credit limit on an account is changed
account.terms.updatedPayment terms on an account are changed

product (21 events)

EventDescription
product.createdA new product is created
product.updatedProduct fields are updated
product.deletedA product is soft-deleted
product.restoredA deleted product is restored
product.variant.createdA product variant is created
product.variant.updatedA product variant is updated
product.variant.deletedA product variant is deleted
product.kit.component.addedA component is added to a kit
product.kit.component.updatedA kit component is updated
product.kit.component.removedA kit component is removed
product.pricing.addedA pricing policy is added to a product
product.pricing.updatedA product pricing policy is updated
product.pricing.removedA product pricing policy is removed
product.vendor.addedA vendor is linked to a product
product.vendor.updatedA product-vendor relationship is updated
product.vendor.removedA vendor is unlinked from a product
product.listing.createdA marketplace listing is created
product.listing.updatedA marketplace listing is updated
product.listing.removedA marketplace listing is removed
product.listing.pausedA marketplace listing is paused
product.listing.resumedA paused marketplace listing is resumed

order (16 events)

EventDescription
order.createdA quote or sales order is created
order.updatedOrder fields are updated
order.confirmedA quote is confirmed as a sales order
order.cancelledAn order is cancelled
order.shippedAn order is marked shipped
order.invoicedA sales order is converted to an invoice
order.partially_fulfilledSome but not all order lines are fulfilled
order.fulfilledAll order lines are fulfilled
order.line_item.addedA line item is added to an order
order.line_item.updatedAn order line item is updated
order.line_item.removedAn order line item is removed
order.tax.recalculatedTax is recalculated for an order
order.revisedA SENT invoice or sales order is revised (a customer-visible field changed, or a line item was added/updated/removed after the document was sent). Payload: order_id, order_number, document_type, revision_count, revised_at, revised_by_user_id, revision_summary, changed_fields[].
order.credit_hold_overriddenA permissioned operator pushed an order past a credit-hold gate with a typed reason
order.credit_hold.override_notifiedFinance-team notification fanout succeeded after a credit-hold override (at least one recipient was emailed)
order.credit_hold.override_notify_failedThe credit-hold override notification fanout failed for every configured recipient (Postmark / DNS / config issue)

invoice (9 events)

EventDescription
invoice.createdAn invoice is created
invoice.updatedInvoice fields are updated
invoice.paidAn invoice is fully paid
invoice.partially_paidA partial payment is applied to an invoice
invoice.voidedAn invoice is voided
invoice.refundedAn invoice is refunded
invoice.overdueAn invoice becomes past its due date
invoice.sentAn invoice is emailed to the customer
invoice.viewed_by_customerA customer views the invoice portal page (future: requires portal beacon)

payment (8 events)

EventDescription
payment.createdA payment record is created
payment.succeededA payment is successfully processed
payment.failedA payment attempt fails
payment.refundedA payment is fully refunded
payment.partially_refundedA partial refund is issued
payment.disputedA payment dispute (chargeback) is opened
payment.dispute.wonA dispute is resolved in your favor
payment.dispute.lostA dispute is resolved against you

inventory (7 events)

EventDescription
inventory.adjustedAn inventory adjustment is posted
inventory.transferredInventory is transferred between locations
inventory.reclassifiedInventory is reclassified
inventory.low_stockOn-hand falls to at or below the product’s reorder point (and is still above zero). Debounced to at most once per product per location per hour.
inventory.out_of_stockOn-hand reaches zero (or below). Debounced to at most once per product per location per hour.
inventory.back_in_stockA product that was at zero on-hand is replenished above zero. Debounced to at most once per product per location per hour.
inventory.cycle_count.completedA cycle count is completed

purchase_order (8 events)

EventDescription
purchase_order.createdA purchase order is created
purchase_order.updatedPurchase order fields are updated
purchase_order.approvedA purchase order is approved
purchase_order.rejectedA purchase order is rejected
purchase_order.receivedAll items on a PO are received
purchase_order.partially_receivedSome items on a PO are received
purchase_order.cancelledA purchase order is cancelled
purchase_order.closedA purchase order is closed

vendor_bill (8 events)

EventDescription
vendor_bill.createdA vendor bill is created
vendor_bill.updatedVendor bill fields are updated
vendor_bill.approvedA vendor bill is approved
vendor_bill.rejectedA vendor bill is rejected
vendor_bill.postedA vendor bill is posted to the GL
vendor_bill.paidA vendor bill is paid
vendor_bill.voidedA vendor bill is voided
vendor_bill.written_offA vendor bill balance is written off as income recognition (GAAP: FASB ASC 405-20). Payload includes amount_written_off and journal_entry_id.

journal_entry and period (5 events)

EventDescription
journal_entry.postedA journal entry is posted to the GL
journal_entry.reversedA journal entry is reversed
journal_entry.source_relinkedA migrated journal entry’s source_id has been corrected from a source-system integer to the Arcus order/invoice UUID (MIGRATION-GL-DIMENSIONS-REPORT-READY 2026-05-21; extended 2026-05-27 to also fire when source_type is upgraded from VERSA_MIGRATION to a canonical value or description is enriched with the order number). Payload includes the journal_entry id.
period.closedAn accounting period is closed
period.reopenedA closed accounting period is reopened

fulfillment (7 events)

EventDescription
fulfillment.createdA fulfillment package is created
fulfillment.label_purchasedA shipping label is purchased
fulfillment.label_voidedA shipping label is voided
fulfillment.shippedA package is marked shipped
fulfillment.in_transitCarrier reports the package is in transit
fulfillment.deliveredCarrier reports delivery
fulfillment.exceptionA carrier delivery exception occurs

return (5 events)

EventDescription
return.createdAn RMA is created
return.receivedReturn items are received back
return.disposedReturned items are disposed
return.refundedA return is refunded
return.cancelledAn RMA is cancelled

connector (7 events)

EventDescription
connector.connectedA connector (Shopify, Amazon, etc.) is connected
connector.disconnectedA connector is disconnected
connector.sync_startedA connector sync run starts
connector.sync_completedA connector sync run completes successfully
connector.sync_failedA connector sync run fails
connector.token_refreshedA connector OAuth token is refreshed
connector.token_expiredA connector OAuth token expires

migration (10 events)

Migration events are registered but only fire when the API-RESOURCE-MIGRATION feature is complete. Until then, subscribers see zero migration.* deliveries.
EventDescription
migration.batch_startedA migration batch import starts
migration.batch_completedA migration batch import completes
migration.batch_failedA migration batch import fails
migration.cutover_initiatedA migration cutover is initiated
migration.cutover_verifiedA cutover verification step passes
migration.cutover_completedCutover completes successfully
migration.cutover_rolled_backA cutover is rolled back
migration.snapshot_takenA pre-cutover data snapshot is taken
migration.freeze_engagedThe source system write freeze is engaged
migration.unfreeze_engagedThe write freeze is lifted

webhook (1 event)

EventDescription
webhook.testSynthetic test event sent by the /test endpoint