# Handle errors Learn how to handle common errors during extension runs. This guide covers how to handle errors when your script calls external endpoints using `endpointFetch`. Before you begin, make sure you’ve [created an extension](https://docs.stripe.com/extensions/custom-actions/build-with-script.md) and set up an [endpoint to invoke](https://docs.stripe.com/extensions/invoke-endpoints.md). > Currently, Stripe only supports invoking endpoints from custom workflow actions. See [Build a custom action with a script](https://docs.stripe.com/extensions/custom-actions/build-with-script.md). You don’t need to import `endpointFetch` because it’s a global function injected by the Stripe runtime. It’s available at runtime only, not in tests or local builds. It throws exceptions on non-2xx HTTP responses, network failures, or misconfigurations. ## Handle errors in a script You can handle recoverable HTTP errors directly in your script. In this example, the script catches a 404 response and falls back to creating the missing resource, while allowing non-recoverable errors such as `ERR_CONFIG` and `ERR_NETWORK` to propagate. ```ts try { const result = await endpointFetch({ endpoint: 'com.my_script.send_notifications', path: '/api/notifications', method: 'POST', body: JSON.stringify({ message: `Payment received from ${customInput.name}`, }), }); const data = JSON.parse(result.body); return { success: true, ts: data.ts }; } catch (e) { if (e.status === 404) { const result = await endpointFetch({ endpoint: 'com.my_script.send_notifications', path: '/api/contacts', method: 'POST', body: JSON.stringify({ id: customInput.id, email: customInput.email }), }); return { contact: JSON.parse(result.body), created: true }; } throw e; } ``` ## Success responses (2xx) The endpoint returns a `2xx` status code. In your script, you get a `result` object: ```ts const result = await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }), }); // result = { ok: true, status: 200, body: '{"ok":true,"channel":"C123","ts":"1234567890.123456"}' } ``` Typical handling: ```ts const result = await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }), }); const data = JSON.parse(result.body); return { posted: true, ts: data.ts }; ``` ## Client errors (4xx) The endpoint rejects the request with a `4xx` status code, such as `400 Bad Request`, `403 Forbidden`, `404 Not Found`, or `429 Rate Limited`. In your script, `endpointFetch` throws: ```ts await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel: '#nonexistent', text: 'hello' }), }); // Throws: Error { // message: "Endpoint returned HTTP 400", // code: "EXT_BAD_REQUEST", // status: 400, // body: '{"ok":false,"error":"channel_not_found"}', // stack: "..." // } ``` The error includes: - `e.message`: `"Endpoint returned HTTP {status}"` - `e.code`: An `EXT_*` code mapped from the HTTP status - `e.status`: The numeric HTTP status code - `e.body`: The response body string, or `null` if the endpoint returns an empty response - `e.stack`: The standard V8 stack trace Typical handling: ```ts try { const result = await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel, text: message }), }); const data = JSON.parse(result.body); return { success: true, ts: data.ts }; } catch (e) { if (e.status === 429) { return { success: false, retriable: true, reason: 'rate_limited' }; } if (e.status) { const error = e.body ? JSON.parse(e.body) : {}; return { success: false, status: e.status, error: error.error }; } throw e; // re-throw non-HTTP errors } ``` ## Server errors (5xx) The endpoint reports a server-side failure with a `5xx` status code, such as `HTTP 500`, `502 Bad Gateway`, or `503 Service Unavailable`. In your script, the error matches `4xx` errors: an exception with `code`, `status`, `body`, and `stack`. `body` might be `null` if the endpoint returns an empty response. ```ts await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }), }); // Throws: Error { // message: "Endpoint returned HTTP 503", // code: "EXT_RESOURCE_UNAVAILABLE", // status: 503, // body: null, // stack: "..." // } ``` Typical handling: ```ts try { const result = await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel, text: message }), }); return { success: true }; } catch (e) { if (e.status >= 500) { return { success: false, retriable: true, status: e.status }; } throw e; } ``` ## Network errors Network errors occur when Stripe accepts the request, but the call to the endpoint fails. That includes: - DNS resolution failure for the endpoint host - Connection refused by the endpoint - Endpoint request timeout If you don’t catch the error, the script run fails with a `runtime_error` error code (non-retryable). In your script: ```ts await endpointFetch({ endpoint: 'slack_api', path: '/chat.postMessage', method: 'POST', body: JSON.stringify({ channel: '#billing-alerts', text: 'Invoice paid' }), }); // Throws: Error { message: "Network error", code: "ERR_NETWORK", stack: "..." } ``` Properties: - `e.message`: Always `"Network error"` - `e.code`: `"ERR_NETWORK"` - `e.stack`: The standard V8 stack trace Hostnames and low-level connection details aren’t exposed to scripts for security reasons. ## Configuration errors Configuration errors occur if Stripe can’t find a resource the script references. That includes: - The endpoint name in the app manifest doesn’t exist - The script ID isn’t found Fix the manifest or deployment. If uncaught, the script run fails with a `bad_request` error code (non-retryable). In your script: ```ts await endpointFetch({ endpoint: 'nonexistent_api', path: '/test', method: 'GET', }); // Throws: Error { message: "Endpoint not found: nonexistent_api", code: "ERR_CONFIG" } ``` The error has the following properties: - `e.message`: Describes what’s missing (for example,`"Script not found: scp_123"`, `"Endpoint not found: slack_api"`, or `"Resource not found"` when details aren’t available) - `e.code`: `"ERR_CONFIG"` There are no `e.status` or `e.body` properties. This isn’t an HTTP response. In this case, let the error propagate. It’s a configuration or deployment issue, not something to handle at runtime in the script. ## Error scenarios summary Stripe doesn’t automatically retry failed `endpointFetch` calls. To retry, handle it in your script. | Scenario | Exception? | `e.code` | `e.status` | `e.body` | Retryable? | | ------------------- | ---------- | -------------------------- | ---------- | -------------------- | ---------- | | `2xx success` | No | N/A | N/A | N/A | N/A | | `400 Bad Request` | Yes | `EXT_BAD_REQUEST` | 400 | Error body or `null` | No | | 401 Unauthorized | Yes | `EXT_UNAUTHORIZED` | 401 | Error body or `null` | No | | `403 Forbidden` | Yes | `EXT_NOT_ALLOWED` | 403 | Error body or `null` | No | | `404 Not Found` | Yes | `EXT_NOT_FOUND` | 404 | Error body or `null` | No | | `429 Rate Limited` | Yes | `EXT_RATE_LIMIT` | 429 | Error body or `null` | Yes | | Other 4xx | Yes | `EXT_BAD_REQUEST` | 4xx | Error body or `null` | No | | HTTP 500 | Yes | `EXT_RUNTIME_ERROR` | 500 | Error body or `null` | No | | `503 Unavailable` | Yes | `EXT_RESOURCE_UNAVAILABLE` | 503 | Error body or `null` | Yes | | `504 Timeout` | Yes | `EXT_TIMEOUT` | 504 | Error body or `null` | Yes | | Other 5xx | Yes | `EXT_RUNTIME_ERROR` | 5xx | Error body or `null` | No | | Network failure | Yes | `ERR_NETWORK` | N/A | N/A | No | | Configuration error | Yes | `ERR_CONFIG` | N/A | N/A | No | ## Next steps - [Build a custom action with a script](https://docs.stripe.com/extensions/custom-actions/build-with-script.md) - [Invoke an endpoint from a script](https://docs.stripe.com/extensions/invoke-endpoints.md) - [Extension points](https://docs.stripe.com/extensions/extension-points.md) - [How extensions work](https://docs.stripe.com/extensions/how-extensions-work.md)