DevStride waits at most 10 seconds for an answer, and sends deliveries to one endpoint one at a time, so the time your receiver takes to answer is the speed of its queue. Do the minimum in the request:
2xx with an empty body.Then do the real work (calling other APIs, updating records, posting to chat) in a separate worker. Answering before the event is stored safely risks losing it if your process crashes in between; answering after storing it is the safe order.
Check X-DevStride-Signature on every request, against the raw body, before you parse it or act on it. Reject failures with 401. A request that fails verification is either not from DevStride or was altered on the way; nothing in it should reach your systems. The Verifying Signatures page has tested code for six languages.
Delivery is at least once, so the same event can arrive twice. Record every event id you have processed and skip repeats:
CREATE TABLE devstride_webhook_events (
event_id uuid PRIMARY KEY,
received_at timestamptz NOT NULL DEFAULT now()
);
-- In the same transaction as the work the event causes:
INSERT INTO devstride_webhook_events (event_id) VALUES ($1)
ON CONFLICT (event_id) DO NOTHING;
-- 0 rows inserted: you have already handled this event; skip it.
Keep ids for at least 30 days, the period over which a delivery can be replayed, then delete old rows. The id is the same on every retry and replay, and the same for every endpoint that receives the event.
Any status other than 2xx is retried for about a day and, while it is, holds up every newer delivery to that endpoint. So:
2xx to event types you do not handle, including ping and types added in future;2xx to an event your code cannot process (a bug, an unexpected value), store it, and alert yourself, rather than letting it block the queue;Within one endpoint deliveries arrive in the order DevStride queued them, but across endpoints there is no order at all, and two changes made almost at once can be queued the other way round. When order matters:
occurredAt and ignore an event older than the state you already hold;previousStatusId matches the status you have on record;New fields and event types are added without notice. Read the fields you need, ignore unknown keys, and never fail on an unknown type. See Compatibility.
comment.created includes internal notes: check visibility is public before showing a comment to anyone outside your team. If your integration also writes to DevStride (for example syncing comments back), run it as a dedicated member and skip events whose origin.actorId is that member's username, so you do not process your own writes as new ones.
Keep each endpoint's secret in a secret manager or environment variable, never in source code or logs. Use a different endpoint, and so a different secret, for each system and environment (production and staging should never share one). Rotate on a schedule and whenever someone who had access leaves; if a secret may have leaked, rotate twice to retire it immediately (why).
DevStride reads at most 64 KB of your response; a larger one counts as a failure even with a 2xx status. An empty 204 No Content is ideal.
The failing and recovered emails go to everyone with Manage Webhooks: make sure that includes someone who will act on them. The red banner and the delivery log on the Webhooks page show what is failing and why. See Failing and recovered alerts.
User-Agent: DevStride-Webhooks/1 identifies the requests in your logs and WAF rules. Anyone can send that header, so never treat it as proof.| Symptom | Likely cause | Fix |
|---|---|---|
Every delivery gets 401 from your receiver | The body was parsed and re-serialized before verifying, or the wrong secret is configured. | Verify the raw bytes (pitfalls by framework); check you copied the whole secret, whsec_ prefix included, for this endpoint. Run your code against the test vector. |
| Signatures fail only on some events | Characters outside plain ASCII (accented names, emoji) are being re-encoded, or a size limit truncates large comment events. | Hash the bytes as received, not a decoded and re-encoded string; allow request bodies of at least 1 MB. |
| Signatures fail on every event after a while, or intermittently | Your server's clock has drifted more than 5 minutes. | Synchronize the clock with NTP. |
| Signatures start failing about a day after a rotation | The receiver still has the previous secret, which stopped being sent 24 hours after the rotation. | Deploy the current secret, or rotate again and deploy that one. |
Deliveries fail with 413 | Your server or proxy limits request size (Express defaults to 100 KB). | Raise the limit to at least 1 MB on the webhook route. |
Last response 301, 302 or 303 | The URL redirects (for example a trailing slash, or www to the apex), and DevStride does not follow redirects that would turn the POST into a GET. | Register the final URL. |
| No response: a timeout or Fetch deadline exceeded | The receiver took longer than 10 seconds to answer, or the whole request took more than 15 seconds. | Answer first and process in the background. |
| No response: Blocked url scheme 'http:' | A redirect sent the request to plain http://. | Serve the endpoint, and any redirect target, over HTTPS only. |
| No response: Blocked request to non-public address | The host now resolves to a private or internal address. | Point the name at a public address, or use a public relay. |
| No response: Host could not be resolved | The DNS name does not exist (or no longer does). | Fix the DNS record or the endpoint URL. |
| No response: a certificate error | The certificate is expired, self-signed or for another name. | Install a certificate from a public certificate authority. |
| Creating or saving an endpoint is refused | The URL is not https://, does not resolve, resolves to a refused address, or the organization already has 20 endpoints. | Read the message under Endpoint URL; fix the URL or delete an unused endpoint. |
A 2xx response still counts as failed | The response body is over 64 KB. | Answer with an empty or short body. |
| Events stopped arriving and nothing shows as failing | The endpoint is disabled (changes are not queued while it is), or it is no longer subscribed to that event type. | Check Enabled and Events in its row. Changes made while it was disabled are not sent; catch up through the API. |
| New events arrive hours late | An older delivery to the same endpoint is failing and waiting for retries, which holds back the ones behind it. | Find the oldest pending row in the delivery log, fix whatever makes it fail, and the queue drains in order. |
| The same event processed twice | A retry or replay after your receiver had already handled it. | Deduplicate by event id. |
Webhook deliveries are copies of your data, so DevStride's privacy tools reach them too:
item.created and comment.created delivery that carries that requester's tickets or words, including those of requesters merged into them, from the delivery log. Deliveries still waiting to be sent are deleted too, so they are never sent.comment.created deliveries the same way.Once deleted, those deliveries can no longer be viewed or replayed. Status and SLA events carry only ids, and are not affected.
Managing Endpoints
Edit, enable, disable, test and delete webhook endpoints, rotate signing secrets with a 24-hour overlap, read the delivery log, and replay one or all failed deliveries, from Settings → Webhooks, the REST API or the DevStride MCP server.
Overview
The developer guide's path from a Mac with nothing on it to your first merged story: what you will have at the end, how long it takes, and where you are needed.