Docs  /  Developer Guide  /  REST API

REST API

Every operation the plugin exposes over HTTP, with the request, the response, and the hooks you can use to alter or extend each one. Cookbook-style.

The basics

Base URL/wp-json/order-updates-for-woo/v1/
Auth header (staff)X-WP-Nonce from wp_create_nonce('wp_rest')
Auth query (guest customer)?order_key=<wc_order_key>
ErrorsWP_Error with code order_updates_for_woo_* + HTTP status

Every endpoint shares the same auth pattern: nonce-verify first, then either capability-check for staff or order-key-check for customers. If you’re writing your own endpoint, copy OrderUpdatesForWoo\API\Concerns\VerifiesAccess — same shape everywhere.

Postman ready. Every example below is a cURL command. Open Postman → New → Import → Raw text, paste the cURL block, hit Import — the request, headers, and body all populate automatically. Swap yoursite.com for your store’s URL and YOUR_NONCE_HERE for a nonce minted via wp_create_nonce('wp_rest').

Auth pattern (every endpoint follows this)

public function can_access( WP_REST_Request $request ): bool|WP_Error {
    if ( $error = $this->verify_nonce( $request ) ) {
        return $error;
    }

    // Staff path
    if ( $this->is_authorized_for_order( $order_id ) ) {
        return true;
    }

    // Customer path (only on customer-facing endpoints)
    if ( $this->viewer_service->is_acting_as_customer( $order_id, $order_key ) ) {
        return true;
    }

    return $this->forbidden_error( 'You are not allowed.' );
}

If you’re calling from your own JS, mint the nonce via wp_create_nonce('wp_rest') and pass it as X-WP-Nonce. If you’re calling from a guest customer flow, pass the WooCommerce order_key as a body parameter or a query string. The plugin will accept either or both.

Create an update

You want to programmatically open a new update on an order — from a CRM webhook, a Slack-to-update bridge, an automated flow that creates a ticket when a high-value order ships, anything.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "order_id":         42,
      "title":            "Refund process discussion",
      "assignee_user_id": 5,
      "internal_note":    "@bob please look into this.",
      "customer_note":    "Hi James, we're looking into this. Back to you soon.",
      "status":           "notice"
  }'

Response

{
    "cardHtml":  "<li class="awts_card">...</li>",
    "updateId":  123,
    "isEdit":    false,
    "message":   "Update created.",
    "noteId":    456
}

Related hooks

  • order_updates_for_woo_before_update_save — runs before the DB write. Return a WP_Error to cancel.
  • order_updates_for_woo_after_update_save — runs after success. $is_edit is false here.
  • order_updates_for_woo_save_update_response — filter the JSON response shape.

Things worth knowing

Only order_id and title are required. Leave everything else off for a minimal stub.

If you pass a customer_note, the plugin auto-flips customer_visible to true and queues the customer notification email. Skip it to keep the update internal until later.

The internal_note field accepts @username mentions inline — mentioned users get the standard mention email and admin-bar badge.

Edit an existing update

Change any field on an update — title, assignee, status, customer-visibility — in one call. Same endpoint as create; just pass update_id.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "update_id":        123,
      "title":            "Refund -- partially issued",
      "assignee_user_id": 7,
      "status":           "warning"
  }
  
  // Returns the same shape as create, with isEdit: true.

Related hooks

  • order_updates_for_woo_before_update_save — runs before the DB write. $is_edit is true.
  • order_updates_for_woo_after_update_save — runs after. Use this for “something changed” webhooks.
  • order_updates_for_woo_save_update_response — filter the response.

Things worth knowing

Only include the fields you want to change. Anything you leave out keeps its current value.

Renaming the title is recorded in the tracking log. If the update is customer-visible and the new title is very different, the customer is not emailed again — renames are silent on purpose. Pass notify_customer: 1 as a body parameter if you want the customer to be emailed about the rename.

Change just the update title

Subset of the edit-update operation, but worth calling out: if your addon syncs ticket titles from an external system (Linear, Jira, etc.), this is your operation.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
        "update_id":        123,
        "title":            "New title"
    }'

Related hooks

  • Same hooks as “Edit an existing update” above.

Things worth knowing

If you’re syncing titles bidirectionally with an external system, dedupe carefully — the after_update_save hook fires whether the title actually changed or not, so a naive sync loop will ping-pong forever. Compare the new title to the previous one inside your hook handler and bail when they match.

Change status

Standalone endpoint specifically for status changes — this is what the admin’s inline status pill click hits. Use this when you only want to flip the colour without touching anything else.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/status' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
        "status": "warning"
    }
    
    // Returns: { cardHtml, updateId }'

Related hooks

  • order_updates_for_woo_change_status_response — filter the JSON response.
  • order_updates_for_woo_status_changed — fires whenever the status actually changes (skipped if old and new are the same).
  • order_updates_for_woo_update_changed — generic "anything was edited" action, fires here too.

Things worth knowing

The status string must match a key from Settings → General → Statuses (default: neutral, notice, warning, resolved). Passing an unknown key returns a 400.

Changing the status posts a system marker into the customer-notes thread (visible to the customer) so they see the progress inline. If you want to suppress that — an automated status sync that shouldn’t notify — you currently can’t. Open an issue if you need that.

Change assignee

Reassign an update. Same endpoint as “edit an update” with only the assignee field passed.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
        "update_id":        123,
        "assignee_user_id": 7   // 0 = unassign
    }'

Related hooks

  • order_updates_for_woo_before_update_save / _after_update_save — lifecycle pair.
  • order_updates_for_woo_update_changed — generic mutation hook.

Things worth knowing

The new assignee gets the standard assignee notification email. The old assignee is not emailed about losing the assignment — if you want to notify them too, hook after_update_save, compare $update['assignee_user_id'] to the previous value via your own bookkeeping, and queue your own email.

Passing 0 unassigns the update.

Add a participant — there’s no endpoint, and that’s on purpose

If you searched for an “add participant” endpoint and found nothing, that’s expected. Participants aren’t a separate concept you maintain — the plugin computes the participant list automatically from the people who’ve actually touched the update:

  • The user who created the update
  • The current assignee
  • Every user who has posted a note (internal or customer-facing) on it
  • Every user who’s been @mentioned in an internal note

So “adding a participant” means doing one of those things. To programmatically add someone:

  • Make them the assignee — use the change-assignee endpoint above.
  • Have them post a note — use the add-internal-note or add-customer-note endpoint as that user.
  • Mention them in an internal note — include @username in the note body when you POST /updates/{id}/notes.

Need the current participant list for your own logic? Use the ParticipantResolver helper from your PHP code (see Helper classes) rather than calling REST. There’s no public REST endpoint that returns the resolved list — it’s rendered into the update card’s Participants tab on the server side.

Add an internal note

Post a team-only note to an update’s internal notes tab. Customers never see this.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/notes' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
        "note": "I'll look at this tomorrow. @sarah ping me if it's urgent."
    }''

Response

{
    "id":          789,
    "update_id":   123,
    "note":        "...",
    "created_by":  5,
    "created_at":  "May 22, 3:00 PM",
    "mentions":    [ { "user_id": 7, "name": "sarah" } ]
}

Related hooks

  • order_updates_for_woo_before_add_internal_note / _after_add_internal_note — lifecycle pair, four args.
  • order_updates_for_woo_internal_note_payload — filter the note text before it’s saved (good place to strip secrets, expand macros, etc.).
  • order_updates_for_woo_add_internal_note_response — filter the response.

Things worth knowing

@username mentions are parsed at save. Mentioned users get a separate mention email (different subject so they can filter it apart from generic notifications) and a count badge in the WordPress admin bar.

Mentions bypass the per-update mute toggle — if someone tags you explicitly, you hear about it even if you’ve muted the thread. That’s deliberate.

Edit an internal note

Fix a typo or update the content of an internal note. Edits are preserved — the original text is kept in the revision history.

The call

curl -X PATCH 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/notes/789' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "note": "Updated content."
  }'

Related hooks

  • order_updates_for_woo_before_update_internal_note / _after_update_internal_note.
  • order_updates_for_woo_update_internal_note_response — response filter.

Things worth knowing

Notes are only editable within the configured window after creation (default: 60 minutes), and only by the author. Both rules are enforced server-side — bypassing them via a direct REST call still returns 403.

Delete an internal note

Remove an internal note entirely. The plugin’s settings can lock this down — check Settings → Admin Only → Allow internal note deletion.

The call

curl -X DELETE 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/notes/789' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE'

Related hooks

  • order_updates_for_woo_before_delete_internal_note / _after_delete_internal_note.

Things worth knowing

If “allow internal note deletion” is off in settings, this endpoint returns 403 for everyone except site admins. Don’t fight that gate — the deletion-locked mode is a deliberate audit-trail decision for stores that need one.

Add a customer note (as staff)

Post a message that the customer will see in their portal and receive as an email. The cornerstone of customer-facing communication.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/customer-notes' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "note":            "Your refund has been processed. 3-5 business days.",
      "notify_customer": 1
  }
  
  // Returns the saved note + a customer_visible_changed flag (true on first customer note).'

Related hooks

  • order_updates_for_woo_before_add_customer_note / _after_add_customer_note — lifecycle pair.
  • order_updates_for_woo_customer_note_payload — filter the note text before save.
  • order_updates_for_woo_add_customer_note_response — response filter.

Things worth knowing

Posting the first customer note on an update auto-flips customer_visible to true — the customer can now see the whole update, not just this note.

notify_customer: 0 saves the note silently. Use this if you’re backfilling messages from another system and don’t want to spam the customer with old emails. The note still appears in the customer’s portal the next time they visit; only the email is suppressed.

Add a customer note (as the customer)

Same endpoint, called from the customer’s portal JS or a guest replying to the email link. The only difference is auth: pass order_key instead of a staff capability.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/customer-notes' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "note":      "Thanks, I still don't see the refund in my account.",
      "order_key": "wc_order_AbCdEf123"
  }'

Related hooks

  • Same hooks as the staff-side call.

Things worth knowing

The endpoint figures out automatically whether the request is staff or customer based on auth. The hooks fire identically — you can’t tell the difference from after_add_customer_note alone. If you need to branch, check the current user’s capability inside your handler (no edit_shop_order = customer).

Edit a customer note

Fix a typo on a recently-posted customer note. Subject to the configured edit window (default: 5 minutes for customers, 60 for staff).

The call

curl -X PATCH 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/customer-notes/789' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "note":      "Corrected: refund in 5-7 business days.",
      "order_key": "wc_order_AbCdEf123"
  }'

Related hooks

  • order_updates_for_woo_before_update_customer_note / _after_update_customer_note.
  • order_updates_for_woo_update_customer_note_response.

Things worth knowing

Every revision is preserved. Fetch the history via GET /updates/{id}/customer-notes/{note_id}/history — useful if you want to show a diff or audit the rewrite.

Once a newer note arrives in the thread (from staff or the customer), the edit window closes for the older note. This keeps the history record clean.

Re-notify the customer about a customer note

Manually re-send the email for a customer note. Use this if the original email bounced and you’ve since updated the customer’s address, or if you want to nudge them about a message they haven’t responded to.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/customer-notes/789/notify' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE'

Related hooks

  • order_updates_for_woo_before_notify_customer / _after_notify_customer.
  • order_updates_for_woo_notify_customer_response.

Things worth knowing

This bypasses the customer’s per-order email preference. If they’ve opted out, that’s usually for a reason — check with them before forcing a notification through.

Upload an attachment

Attach a file to a note or to a new note you’re about to create. Multipart form data, served back through a signed URL.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/attachments' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d 'Content-Type: multipart/form-data
  
  file:        <binary>
  update_id:   123
  note_id:     789           // optional -- attach to an existing note
  note_type:   customer      // or "internal"
  order_key:   wc_order_...  // for customer-side uploads'

Related hooks

  • order_updates_for_woo_before_upload_attachment / _after_upload_attachment.
  • order_updates_for_woo_attachment_upload_context — filter the upload context (storage path, MIME allowlist).
  • order_updates_for_woo_attachment_storage_handler — replace the storage backend entirely (S3, Cloudinary, etc.).
  • order_updates_for_woo_upload_attachment_response — response filter.

Things worth knowing

MIME types are validated against the active allowlist (configurable under Settings → General → Attachments). Executable types are blocked server-side regardless of settings.

Files are stored under wp-content/uploads/order-updates-for-woo/orders/{order_id}/{update_id}/{note_id}/ with UUID filenames. The customer’s original filename is preserved in the database as a display name; it never appears in the storage path or URL.

Downloads are gated — staff via REST nonce + capability, customers via a short-lived HMAC-signed URL. Direct filesystem access is blocked by .htaccess + index.html guards at every directory level.

Delete an attachment

Remove a previously-uploaded file. Cleans up both the database row and the on-disk file.

The call

curl -X DELETE 'https://yoursite.com/wp-json/order-updates-for-woo/v1/attachments/42' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE'

Related hooks

  • order_updates_for_woo_attachment_before_delete — fires before the DB row + file go.
  • order_updates_for_woo_before_delete_attachment / _after_delete_attachment — the standard before/after pair.
  • order_updates_for_woo_attachment_delete — filter that lets you take over the deletion (return true to skip the plugin’s default cleanup).
  • order_updates_for_woo_delete_attachment_response — response filter.

Things worth knowing

If the delete leaves the note dir, update dir, or order dir empty, the plugin walks up and prunes those directories too — no hollow folders left behind.

If your addon stores attachments in an external service via attachment_storage_handler, also hook attachment_delete to clean up the remote copy.

Mark an update as solved

Close out an update. Triggers the customer rating widget (if customer-visible) and the standard resolution flow.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/solve' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "notify_customer": 1
  }
  
  // Returns: { cardHtml, updateId, isEdit: true }'

Related hooks

  • order_updates_for_woo_before_mark_solved / _after_mark_solved.
  • order_updates_for_woo_mark_solved_response.

Things worth knowing

If the update is customer-visible, you must pick “silent solve” or “notify and solve” via the notify_customer flag. Silent solves skip the customer email but still expose the rating widget on the portal.

The status flips to your designated “solved” status (default: resolved). solved_at is set to the current UTC time.

Re-open a solved update

Bring a solved update back to life. Fires whether the admin clicks Re-open or the customer hits “Still has issue?”.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/reopen' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "order_key": "wc_order_AbCdEf123"   // optional -- only when the customer initiates
  }'

Related hooks

  • order_updates_for_woo_before_reopen_update / _after_reopen_update.
  • order_updates_for_woo_reopen_update_response.

Things worth knowing

To tell staff reopens from customer reopens inside your hook, check whether order_key is present in the request (customer-side requests always carry it; staff requests don’t).

Reopening an update that already has a rating is currently blocked — rated updates are treated as closed. If you want a customer-initiated “I rated but it’s still broken” flow, the right pattern is a new update on the same order.

Submit a customer rating

Customer endpoint — called from the rating widget on the customer’s portal.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/rating' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "stars":     4,
      "comment":   "Fast response, all sorted.",
      "order_key": "wc_order_AbCdEf123"
  }'

Related hooks

  • order_updates_for_woo_before_customer_rating / _after_customer_rating.
  • order_updates_for_woo_customer_rating_response.

Things worth knowing

One rating per update, ever. Re-submitting returns a 409. If you need to update a rating (manual correction, say), do it via direct database access from your addon — the public REST API doesn’t allow it.

4 and 5 star ratings trigger the promoter follow-up email (with social share buttons). 1, 2, and 3 star ratings trigger the detractor empathy email + a low-rating alert to the site admin.

Delete an update

Remove an update entirely. Preserves a snapshot in the order’s deletion audit log for compliance.

The call

curl -X DELETE 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "notify_customer": 0
  }'

Related hooks

  • order_updates_for_woo_before_delete_update / _after_delete_update$update contains the snapshot, the DB row is gone after.
  • order_updates_for_woo_delete_update_response.
  • order_updates_for_woo_update_deleted — lower-level “something was deleted” action.

Things worth knowing

The plugin writes a complete snapshot to the order’s deletion-audit table before this hook fires — the deletion isn’t lost, even if the live row is.

Attachments belonging to the deleted update are scheduled for cleanup right after the hook. If your addon archives data to S3, mirror in this handler before the files are gone.

Inject a custom notification into the admin bar

The admin bar badge shows unread mentions and assignments. You can push your own items into it — CRM tasks, escalations, anything time-sensitive your team should see on every admin page.

The call

use OrderUpdatesForWoo\Helpers\AdminBarNotificationStore;

AdminBarNotificationStore::push( $user_id, [
    'id'    => 'my-addon-escalation-' . $escalation_id,
    'type'  => 'escalation',
    'label' => sprintf( __( 'New escalation on order #%s', 'my-addon' ), $order_number ),
    'href'  => admin_url( 'admin.php?page=wc-orders&action=edit&id=' . $order_id ),
    'at'    => current_time( 'mysql', true ),
] );

Related hooks

  • order_updates_for_woo_admin_bar_mention — fires whenever an item is pushed. Hook this if you want to mirror to Slack or webhooks.

Things worth knowing

This isn’t a REST endpoint — it’s a server-side helper that adds an item to the user’s pending-notification queue. The next time the admin bar Heartbeat runs (within 30 seconds), they’ll see the badge count increment and your item in the dropdown.

id must be unique per-user; pushing the same id twice does nothing (the user already has it). Items are removed when the user clicks through to the linked page, so use the URL as the “where to find this”.

List updates on an order

Get the full list of updates for an order, with notes, ratings, attachments inline.

The call

curl -X GET 'https://yoursite.com/wp-json/order-updates-for-woo/v1/order-updates?order_id=42'

Related hooks

  • order_updates_for_woo_order_updates_list_response — filter the JSON response.

Things worth knowing

Used by the admin meta box on page load. The response is cache-friendly — later calls inside the cache window are served from wp_cache.

Get a single update

Refresh one specific update without re-fetching the whole order’s list.

The call

curl -X GET 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123'

Related hooks

  • order_updates_for_woo_single_update_response — response filter.

Things worth knowing

Useful when your addon’s JS needs a fresh card after some mutation. Returns the same shape as the create/edit endpoints — { update, cardHtml }.

Poll the customer thread for new messages

The customer portal hits this every 30 seconds to pick up new staff replies without a page refresh. Returns any new or edited notes since a cursor.

The call

curl -X GET 'https://yoursite.com/wp-json/order-updates-for-woo/v1/customer-thread/poll?order_id=42&since_note_id=789&since_time=2026-05-22T15:00:00Z&order_key=wc_order_...'

Response

{
    "notes":              [ /* new or edited notes */ ],
    "resolved_update_ids":[ 123, 456 ],
    "open_update_ids":    [ 789 ],
    "latest_update_id":   1234,
    "server_time":        "2026-05-22T15:00:30Z"
}

Related hooks

  • order_updates_for_woo_poll_customer_thread_response — response filter.
  • order_updates_for_woo_poll_interval_min / _mid / _max — filter the adaptive backoff intervals.

Things worth knowing

The endpoint is short-cache friendly — responses are keyed by order_id + since_note_id and cached for 15 seconds in a transient. Two customers watching the same order share one DB query.

If you’ve replaced polling with WebSockets via order_updates_for_woo_realtime_config, the customer JS skips this endpoint entirely while the WebSocket is connected, then falls back to it if the socket drops.

Toggle notifications for a single update

The “Get notifications” switch in the upper-right of each update card writes to user-meta, scoped to that update only. Use this endpoint to flip it programmatically — for example, an addon that auto-mutes you out of updates assigned to other team members.

The call

curl -X POST 'https://yoursite.com/wp-json/order-updates-for-woo/v1/updates/123/staff-email-preference' \
  -H 'X-WP-Nonce: YOUR_NONCE_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
      "muted": true
  }
  
  // Returns: { muted: true }'

Related hooks

  • order_updates_for_woo_before_save_staff_email_preference / _after_save_staff_email_preference — lifecycle pair around the mute toggle.
  • order_updates_for_woo_save_staff_email_preference_response — filter the JSON response.

Things worth knowing

The mute applies to your own emails on this update only. Other team members’ preferences are untouched. @mentions skip the mute on purpose — if someone @-tags you on a muted thread, you still get the mention email. That’s the rule; if your add-on queues its own emails, follow the same exception.

For the per-customer per-order opt-out (the customer-facing equivalent), call POST /customer-email-preference with { order_id, enabled }. Same auth pattern (customer via order_key or staff via capability), same hook shape (before_save_customer_email_preference / after).

The remaining endpoints

The operations above cover the common cases. The full inventory — analytics endpoints, settings save endpoints, attachment-history endpoints, etc. — is in Reference → All REST endpoints with the request/response shape for each.